blob: 6b0ecc9d68e5d96175332c3d408e35e08b39464f [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
Cheryl Sabella7028e592017-08-26 14:26:02 -040014from tkinter import (Toplevel, Listbox, Text, 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
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -040036
terryjreedy349abd92017-07-07 16:00:57 -040037changes = ConfigChanges()
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -040038# Reload changed options in the following classes.
Tal Einat604e7b92018-09-25 15:10:14 +030039reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
40 Squeezer)
terryjreedy349abd92017-07-07 16:00:57 -040041
csabella5b591542017-07-28 14:40:59 -040042
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000043class ConfigDialog(Toplevel):
csabella7eb58832017-07-04 21:30:58 -040044 """Config dialog for IDLE.
45 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000046
Terry Jan Reedybfebfd82017-09-30 17:37:53 -040047 def __init__(self, parent, title='', *, _htest=False, _utest=False):
csabella7eb58832017-07-04 21:30:58 -040048 """Show the tabbed dialog for user configuration.
49
csabella36329a42017-07-13 23:32:01 -040050 Args:
51 parent - parent of this dialog
52 title - string which is the title of this popup dialog
53 _htest - bool, change box location when running htest
54 _utest - bool, don't wait_window when running unittest
55
56 Note: Focus set on font page fontlist.
57
58 Methods:
59 create_widgets
60 cancel: Bound to DELETE_WINDOW protocol.
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040061 """
Steven M. Gavad721c482001-07-31 10:46:53 +000062 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040063 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040064 if _htest:
65 parent.instance_dict = {}
Louie Lu9b622fb2017-07-14 08:35:48 +080066 if not _utest:
67 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000068
Steven M. Gavad721c482001-07-31 10:46:53 +000069 self.configure(borderwidth=5)
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:
csabellae8eb17b2017-07-30 18:39:17 -040099 note: Notebook
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400100 highpage: HighPage
csabellae8eb17b2017-07-30 18:39:17 -0400101 fontpage: FontPage
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400102 keyspage: KeysPage
csabellae8eb17b2017-07-30 18:39:17 -0400103 genpage: GenPage
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400104 extpage: self.create_page_extensions
csabella36329a42017-07-13 23:32:01 -0400105
106 Methods:
csabella36329a42017-07-13 23:32:01 -0400107 create_action_buttons
108 load_configs: Load pages except for extensions.
csabella36329a42017-07-13 23:32:01 -0400109 activate_config_changes: Tell editors to reload.
110 """
Terry Jan Reedyd6e2f262017-09-19 19:01:45 -0400111 self.note = note = Notebook(self)
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400112 self.highpage = HighPage(note)
csabella9397e2a2017-07-30 13:34:25 -0400113 self.fontpage = FontPage(note, self.highpage)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400114 self.keyspage = KeysPage(note)
csabellae8eb17b2017-07-30 18:39:17 -0400115 self.genpage = GenPage(note)
csabella9397e2a2017-07-30 13:34:25 -0400116 self.extpage = self.create_page_extensions()
117 note.add(self.fontpage, text='Fonts/Tabs')
118 note.add(self.highpage, text='Highlights')
119 note.add(self.keyspage, text=' Keys ')
120 note.add(self.genpage, text=' General ')
121 note.add(self.extpage, text='Extensions')
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400122 note.enable_traversal()
123 note.pack(side=TOP, expand=TRUE, fill=BOTH)
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400124 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400125
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400126 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400127 """Return frame of action buttons for dialog.
128
129 Methods:
130 ok
131 apply
132 cancel
133 help
134
135 Widget Structure:
136 outer: Frame
137 buttons: Frame
138 (no assignment): Button (ok)
139 (no assignment): Button (apply)
140 (no assignment): Button (cancel)
141 (no assignment): Button (help)
142 (no assignment): Frame
143 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400144 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400145 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400146 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400147 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000148 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400149 padding_args = {'padding': (6, 3)}
150 outer = Frame(self, padding=2)
151 buttons = Frame(outer, padding=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400152 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400153 ('Ok', self.ok),
154 ('Apply', self.apply),
155 ('Cancel', self.cancel),
156 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400157 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
csabellabac7d332017-06-26 17:46:26 -0400158 **padding_args).pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400159 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400160 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
161 buttons.pack(side=BOTTOM)
162 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400163
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400164 def ok(self):
165 """Apply config changes, then dismiss dialog.
166
167 Methods:
168 apply
169 destroy: inherited
170 """
171 self.apply()
172 self.destroy()
173
174 def apply(self):
175 """Apply config changes and leave dialog open.
176
177 Methods:
178 deactivate_current_config
179 save_all_changed_extensions
180 activate_config_changes
181 """
182 self.deactivate_current_config()
183 changes.save_all()
184 self.save_all_changed_extensions()
185 self.activate_config_changes()
186
187 def cancel(self):
188 """Dismiss config dialog.
189
190 Methods:
191 destroy: inherited
192 """
193 self.destroy()
194
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300195 def destroy(self):
196 global font_sample_text
197 font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
Tal Einat10ea9402018-08-02 09:18:29 +0300198 self.grab_release()
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300199 super().destroy()
200
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400201 def help(self):
202 """Create textview for config dialog help.
203
Xtreakd9677f32019-06-03 09:51:15 +0530204 Attributes accessed:
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400205 note
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400206
207 Methods:
208 view_text: Method from textview module.
209 """
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400210 page = self.note.tab(self.note.select(), option='text').strip()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400211 view_text(self, title='Help for IDLE preferences',
212 text=help_common+help_pages.get(page, ''))
213
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400214 def deactivate_current_config(self):
215 """Remove current key bindings.
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400216 Iterate over window instances defined in parent and remove
217 the keybindings.
218 """
219 # Before a config is saved, some cleanup of current
220 # config must be done - remove the previous keybindings.
221 win_instances = self.parent.instance_dict.keys()
222 for instance in win_instances:
223 instance.RemoveKeybindings()
224
225 def activate_config_changes(self):
226 """Apply configuration changes to current windows.
227
228 Dynamically update the current parent window instances
229 with some of the configuration changes.
230 """
231 win_instances = self.parent.instance_dict.keys()
232 for instance in win_instances:
233 instance.ResetColorizer()
234 instance.ResetFont()
235 instance.set_notabs_indentwidth()
236 instance.ApplyKeybindings()
237 instance.reset_help_menu_entries()
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -0400238 for klass in reloadables:
239 klass.reload()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400240
csabellabac7d332017-06-26 17:46:26 -0400241 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400242 """Part of the config dialog used for configuring IDLE extensions.
243
244 This code is generic - it works for any and all IDLE extensions.
245
246 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -0400247 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400248 GUI interface to change the configuration values, and saves the
249 changes using idleConf.
250
251 Not all changes take effect immediately - some may require restarting IDLE.
252 This depends on each extension's implementation.
253
254 All values are treated as text, and it is up to the user to supply
255 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +0300256 which are boolean, and can be toggled with a True/False button.
csabella36329a42017-07-13 23:32:01 -0400257
258 Methods:
Ville Skyttä49b27342017-08-03 09:00:59 +0300259 load_extensions:
csabella36329a42017-07-13 23:32:01 -0400260 extension_selected: Handle selection from list.
261 create_extension_frame: Hold widgets for one extension.
262 set_extension_value: Set in userCfg['extensions'].
263 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400264 """
265 parent = self.parent
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400266 frame = Frame(self.note)
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400267 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
268 self.ext_userCfg = idleConf.userCfg['extensions']
269 self.is_int = self.register(is_int)
270 self.load_extensions()
csabella7eb58832017-07-04 21:30:58 -0400271 # Create widgets - a listbox shows all available extensions, with the
272 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400273 self.extension_names = StringVar(self)
274 frame.rowconfigure(0, weight=1)
275 frame.columnconfigure(2, weight=1)
276 self.extension_list = Listbox(frame, listvariable=self.extension_names,
277 selectmode='browse')
278 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
279 scroll = Scrollbar(frame, command=self.extension_list.yview)
280 self.extension_list.yscrollcommand=scroll.set
281 self.details_frame = LabelFrame(frame, width=250, height=250)
282 self.extension_list.grid(column=0, row=0, sticky='nws')
283 scroll.grid(column=1, row=0, sticky='ns')
284 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
Cheryl Sabella7028e592017-08-26 14:26:02 -0400285 frame.configure(padding=10)
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400286 self.config_frame = {}
287 self.current_extension = None
288
289 self.outerframe = self # TEMPORARY
290 self.tabbed_page_set = self.extension_list # TEMPORARY
291
csabella7eb58832017-07-04 21:30:58 -0400292 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400293 ext_names = ''
294 for ext_name in sorted(self.extensions):
295 self.create_extension_frame(ext_name)
296 ext_names = ext_names + '{' + ext_name + '} '
297 self.extension_names.set(ext_names)
298 self.extension_list.selection_set(0)
299 self.extension_selected(None)
300
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400301 return frame
302
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400303 def load_extensions(self):
304 "Fill self.extensions with data from the default and user configs."
305 self.extensions = {}
306 for ext_name in idleConf.GetExtensions(active_only=False):
wohlganger58fc71c2017-09-10 16:19:47 -0500307 # Former built-in extensions are already filtered out.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400308 self.extensions[ext_name] = []
309
310 for ext_name in self.extensions:
311 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
312
csabella7eb58832017-07-04 21:30:58 -0400313 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400314 enables = [opt_name for opt_name in opt_list
315 if opt_name.startswith('enable')]
316 for opt_name in enables:
317 opt_list.remove(opt_name)
318 opt_list = enables + opt_list
319
320 for opt_name in opt_list:
321 def_str = self.ext_defaultCfg.Get(
322 ext_name, opt_name, raw=True)
323 try:
324 def_obj = {'True':True, 'False':False}[def_str]
325 opt_type = 'bool'
326 except KeyError:
327 try:
328 def_obj = int(def_str)
329 opt_type = 'int'
330 except ValueError:
331 def_obj = def_str
332 opt_type = None
333 try:
334 value = self.ext_userCfg.Get(
335 ext_name, opt_name, type=opt_type, raw=True,
336 default=def_obj)
csabella7eb58832017-07-04 21:30:58 -0400337 except ValueError: # Need this until .Get fixed.
338 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400339 var = StringVar(self)
340 var.set(str(value))
341
342 self.extensions[ext_name].append({'name': opt_name,
343 'type': opt_type,
344 'default': def_str,
345 'value': value,
346 'var': var,
347 })
348
349 def extension_selected(self, event):
csabella7eb58832017-07-04 21:30:58 -0400350 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400351 newsel = self.extension_list.curselection()
352 if newsel:
353 newsel = self.extension_list.get(newsel)
354 if newsel is None or newsel != self.current_extension:
355 if self.current_extension:
356 self.details_frame.config(text='')
357 self.config_frame[self.current_extension].grid_forget()
358 self.current_extension = None
359 if newsel:
360 self.details_frame.config(text=newsel)
361 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
362 self.current_extension = newsel
363
364 def create_extension_frame(self, ext_name):
365 """Create a frame holding the widgets to configure one extension"""
366 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
367 self.config_frame[ext_name] = f
368 entry_area = f.interior
csabella7eb58832017-07-04 21:30:58 -0400369 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400370 for row, opt in enumerate(self.extensions[ext_name]):
csabella7eb58832017-07-04 21:30:58 -0400371 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400372 label = Label(entry_area, text=opt['name'])
373 label.grid(row=row, column=0, sticky=NW)
374 var = opt['var']
375 if opt['type'] == 'bool':
Cheryl Sabella7028e592017-08-26 14:26:02 -0400376 Checkbutton(entry_area, variable=var,
377 onvalue='True', offvalue='False', width=8
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400378 ).grid(row=row, column=1, sticky=W, padx=7)
379 elif opt['type'] == 'int':
380 Entry(entry_area, textvariable=var, validate='key',
Terry Jan Reedy800415e2018-06-11 14:14:32 -0400381 validatecommand=(self.is_int, '%P'), width=10
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400382 ).grid(row=row, column=1, sticky=NSEW, padx=7)
383
Terry Jan Reedy800415e2018-06-11 14:14:32 -0400384 else: # type == 'str'
385 # Limit size to fit non-expanding space with larger font.
386 Entry(entry_area, textvariable=var, width=15
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400387 ).grid(row=row, column=1, sticky=NSEW, padx=7)
388 return
389
390 def set_extension_value(self, section, opt):
csabella7eb58832017-07-04 21:30:58 -0400391 """Return True if the configuration was added or changed.
392
393 If the value is the same as the default, then remove it
394 from user config file.
395 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400396 name = opt['name']
397 default = opt['default']
398 value = opt['var'].get().strip() or default
399 opt['var'].set(value)
400 # if self.defaultCfg.has_section(section):
csabella7eb58832017-07-04 21:30:58 -0400401 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400402 if (value == default):
403 return self.ext_userCfg.RemoveOption(section, name)
csabella7eb58832017-07-04 21:30:58 -0400404 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400405 return self.ext_userCfg.SetOption(section, name, value)
406
407 def save_all_changed_extensions(self):
csabella36329a42017-07-13 23:32:01 -0400408 """Save configuration changes to the user config file.
409
410 Attributes accessed:
411 extensions
412
413 Methods:
414 set_extension_value
415 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400416 has_changes = False
417 for ext_name in self.extensions:
418 options = self.extensions[ext_name]
419 for opt in options:
420 if self.set_extension_value(ext_name, opt):
421 has_changes = True
422 if has_changes:
423 self.ext_userCfg.Save()
424
425
csabella6f446be2017-08-01 00:24:07 -0400426# class TabPage(Frame): # A template for Page classes.
427# def __init__(self, master):
428# super().__init__(master)
429# self.create_page_tab()
430# self.load_tab_cfg()
431# def create_page_tab(self):
432# # Define tk vars and register var and callback with tracers.
433# # Create subframes and widgets.
434# # Pack widgets.
435# def load_tab_cfg(self):
436# # Initialize widgets with data from idleConf.
437# def var_changed_var_name():
438# # For each tk var that needs other than default callback.
439# def other_methods():
440# # Define tab-specific behavior.
441
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300442font_sample_text = (
443 '<ASCII/Latin1>\n'
444 'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
445 '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
446 '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
447 '\n<IPA,Greek,Cyrillic>\n'
448 '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
449 '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
450 '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
451 '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
452 '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
453 '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
454 '\n<Hebrew, Arabic>\n'
455 '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
456 '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
457 '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
458 '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
459 '\n<Devanagari, Tamil>\n'
460 '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
461 '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
462 '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
463 '\u0b85\u0b87\u0b89\u0b8e\n'
464 '\n<East Asian>\n'
465 '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
466 '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
467 '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
468 '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
469 )
470
csabella6f446be2017-08-01 00:24:07 -0400471
csabella9397e2a2017-07-30 13:34:25 -0400472class FontPage(Frame):
473
csabella6f446be2017-08-01 00:24:07 -0400474 def __init__(self, master, highpage):
475 super().__init__(master)
csabella9397e2a2017-07-30 13:34:25 -0400476 self.highlight_sample = highpage.highlight_sample
477 self.create_page_font_tab()
478 self.load_font_cfg()
479 self.load_tab_cfg()
480
481 def create_page_font_tab(self):
482 """Return frame of widgets for Font/Tabs tab.
483
484 Fonts: Enable users to provisionally change font face, size, or
485 boldness and to see the consequence of proposed choices. Each
486 action set 3 options in changes structuree and changes the
487 corresponding aspect of the font sample on this page and
488 highlight sample on highlight page.
489
490 Function load_font_cfg initializes font vars and widgets from
491 idleConf entries and tk.
492
493 Fontlist: mouse button 1 click or up or down key invoke
494 on_fontlist_select(), which sets var font_name.
495
496 Sizelist: clicking the menubutton opens the dropdown menu. A
497 mouse button 1 click or return key sets var font_size.
498
499 Bold_toggle: clicking the box toggles var font_bold.
500
501 Changing any of the font vars invokes var_changed_font, which
502 adds all 3 font options to changes and calls set_samples.
503 Set_samples applies a new font constructed from the font vars to
Leo Ariasc3d95082018-02-03 18:36:10 -0600504 font_sample and to highlight_sample on the highlight page.
csabella9397e2a2017-07-30 13:34:25 -0400505
506 Tabs: Enable users to change spaces entered for indent tabs.
507 Changing indent_scale value with the mouse sets Var space_num,
508 which invokes the default callback to add an entry to
509 changes. Load_tab_cfg initializes space_num to default.
510
Cheryl Sabella2f896462017-08-14 21:21:43 -0400511 Widgets for FontPage(Frame): (*) widgets bound to self
512 frame_font: LabelFrame
513 frame_font_name: Frame
514 font_name_title: Label
515 (*)fontlist: ListBox - font_name
516 scroll_font: Scrollbar
517 frame_font_param: Frame
518 font_size_title: Label
519 (*)sizelist: DynOptionMenu - font_size
520 (*)bold_toggle: Checkbutton - font_bold
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400521 frame_sample: LabelFrame
522 (*)font_sample: Label
Cheryl Sabella2f896462017-08-14 21:21:43 -0400523 frame_indent: LabelFrame
524 indent_title: Label
525 (*)indent_scale: Scale - space_num
csabella9397e2a2017-07-30 13:34:25 -0400526 """
csabella6f446be2017-08-01 00:24:07 -0400527 self.font_name = tracers.add(StringVar(self), self.var_changed_font)
528 self.font_size = tracers.add(StringVar(self), self.var_changed_font)
529 self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
csabella9397e2a2017-07-30 13:34:25 -0400530 self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
531
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400532 # Define frames and widgets.
csabella9397e2a2017-07-30 13:34:25 -0400533 frame_font = LabelFrame(
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400534 self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ')
535 frame_sample = LabelFrame(
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300536 self, borderwidth=2, relief=GROOVE,
537 text=' Font Sample (Editable) ')
csabella9397e2a2017-07-30 13:34:25 -0400538 frame_indent = LabelFrame(
csabella6f446be2017-08-01 00:24:07 -0400539 self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
csabella9397e2a2017-07-30 13:34:25 -0400540 # frame_font.
541 frame_font_name = Frame(frame_font)
542 frame_font_param = Frame(frame_font)
543 font_name_title = Label(
544 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400545 self.fontlist = Listbox(frame_font_name, height=15,
csabella9397e2a2017-07-30 13:34:25 -0400546 takefocus=True, exportselection=FALSE)
547 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
548 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
549 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
550 scroll_font = Scrollbar(frame_font_name)
551 scroll_font.config(command=self.fontlist.yview)
552 self.fontlist.config(yscrollcommand=scroll_font.set)
553 font_size_title = Label(frame_font_param, text='Size :')
554 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
555 self.bold_toggle = Checkbutton(
556 frame_font_param, variable=self.font_bold,
557 onvalue=1, offvalue=0, text='Bold')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400558 # frame_sample.
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300559 self.font_sample = Text(frame_sample, width=20, height=20)
560 self.font_sample.insert(END, font_sample_text)
csabella9397e2a2017-07-30 13:34:25 -0400561 # frame_indent.
562 indent_title = Label(
563 frame_indent, justify=LEFT,
564 text='Python Standard: 4 Spaces!')
565 self.indent_scale = Scale(
566 frame_indent, variable=self.space_num,
567 orient='horizontal', tickinterval=2, from_=2, to=16)
568
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400569 # Grid and pack widgets:
570 self.columnconfigure(1, weight=1)
571 frame_font.grid(row=0, column=0, padx=5, pady=5)
572 frame_sample.grid(row=0, column=1, rowspan=2, padx=5, pady=5,
573 sticky='nsew')
574 frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
csabella9397e2a2017-07-30 13:34:25 -0400575 # frame_font.
576 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
577 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
578 font_name_title.pack(side=TOP, anchor=W)
579 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
580 scroll_font.pack(side=LEFT, fill=Y)
581 font_size_title.pack(side=LEFT, anchor=W)
582 self.sizelist.pack(side=LEFT, anchor=W)
583 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400584 # frame_sample.
csabella9397e2a2017-07-30 13:34:25 -0400585 self.font_sample.pack(expand=TRUE, fill=BOTH)
586 # frame_indent.
csabella9397e2a2017-07-30 13:34:25 -0400587 indent_title.pack(side=TOP, anchor=W, padx=5)
588 self.indent_scale.pack(side=TOP, padx=5, fill=X)
589
csabella9397e2a2017-07-30 13:34:25 -0400590 def load_font_cfg(self):
591 """Load current configuration settings for the font options.
592
593 Retrieve current font with idleConf.GetFont and font families
594 from tk. Setup fontlist and set font_name. Setup sizelist,
595 which sets font_size. Set font_bold. Call set_samples.
596 """
597 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
598 font_name = configured_font[0].lower()
599 font_size = configured_font[1]
600 font_bold = configured_font[2]=='bold'
601
602 # Set editor font selection list and font_name.
603 fonts = list(tkFont.families(self))
604 fonts.sort()
605 for font in fonts:
606 self.fontlist.insert(END, font)
607 self.font_name.set(font_name)
608 lc_fonts = [s.lower() for s in fonts]
609 try:
610 current_font_index = lc_fonts.index(font_name)
611 self.fontlist.see(current_font_index)
612 self.fontlist.select_set(current_font_index)
613 self.fontlist.select_anchor(current_font_index)
614 self.fontlist.activate(current_font_index)
615 except ValueError:
616 pass
617 # Set font size dropdown.
618 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
619 '16', '18', '20', '22', '25', '29', '34', '40'),
620 font_size)
621 # Set font weight.
622 self.font_bold.set(font_bold)
623 self.set_samples()
624
625 def var_changed_font(self, *params):
626 """Store changes to font attributes.
627
628 When one font attribute changes, save them all, as they are
629 not independent from each other. In particular, when we are
630 overriding the default font, we need to write out everything.
631 """
632 value = self.font_name.get()
633 changes.add_option('main', 'EditorWindow', 'font', value)
634 value = self.font_size.get()
635 changes.add_option('main', 'EditorWindow', 'font-size', value)
636 value = self.font_bold.get()
637 changes.add_option('main', 'EditorWindow', 'font-bold', value)
638 self.set_samples()
639
640 def on_fontlist_select(self, event):
641 """Handle selecting a font from the list.
642
643 Event can result from either mouse click or Up or Down key.
644 Set font_name and example displays to selection.
645 """
646 font = self.fontlist.get(
647 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
648 self.font_name.set(font.lower())
649
650 def set_samples(self, event=None):
651 """Update update both screen samples with the font settings.
652
653 Called on font initialization and change events.
654 Accesses font_name, font_size, and font_bold Variables.
Leo Ariasc3d95082018-02-03 18:36:10 -0600655 Updates font_sample and highlight page highlight_sample.
csabella9397e2a2017-07-30 13:34:25 -0400656 """
657 font_name = self.font_name.get()
658 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
659 new_font = (font_name, self.font_size.get(), font_weight)
660 self.font_sample['font'] = new_font
661 self.highlight_sample['font'] = new_font
662
663 def load_tab_cfg(self):
664 """Load current configuration settings for the tab options.
665
666 Attributes updated:
667 space_num: Set to value from idleConf.
668 """
669 # Set indent sizes.
670 space_num = idleConf.GetOption(
671 'main', 'Indent', 'num-spaces', default=4, type='int')
672 self.space_num.set(space_num)
673
674 def var_changed_space_num(self, *params):
675 "Store change to indentation size."
676 value = self.space_num.get()
677 changes.add_option('main', 'Indent', 'num-spaces', value)
678
679
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400680class HighPage(Frame):
681
682 def __init__(self, master):
683 super().__init__(master)
684 self.cd = master.master
Cheryl Sabella7028e592017-08-26 14:26:02 -0400685 self.style = Style(master)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400686 self.create_page_highlight()
687 self.load_theme_cfg()
688
689 def create_page_highlight(self):
690 """Return frame of widgets for Highlighting tab.
691
692 Enable users to provisionally change foreground and background
693 colors applied to textual tags. Color mappings are stored in
694 complete listings called themes. Built-in themes in
695 idlelib/config-highlight.def are fixed as far as the dialog is
696 concerned. Any theme can be used as the base for a new custom
697 theme, stored in .idlerc/config-highlight.cfg.
698
699 Function load_theme_cfg() initializes tk variables and theme
700 lists and calls paint_theme_sample() and set_highlight_target()
701 for the current theme. Radiobuttons builtin_theme_on and
702 custom_theme_on toggle var theme_source, which controls if the
703 current set of colors are from a builtin or custom theme.
704 DynOptionMenus builtinlist and customlist contain lists of the
705 builtin and custom themes, respectively, and the current item
706 from each list is stored in vars builtin_name and custom_name.
707
708 Function paint_theme_sample() applies the colors from the theme
709 to the tags in text widget highlight_sample and then invokes
710 set_color_sample(). Function set_highlight_target() sets the state
711 of the radiobuttons fg_on and bg_on based on the tag and it also
712 invokes set_color_sample().
713
714 Function set_color_sample() sets the background color for the frame
715 holding the color selector. This provides a larger visual of the
716 color for the current tag and plane (foreground/background).
717
718 Note: set_color_sample() is called from many places and is often
719 called more than once when a change is made. It is invoked when
720 foreground or background is selected (radiobuttons), from
721 paint_theme_sample() (theme is changed or load_cfg is called), and
722 from set_highlight_target() (target tag is changed or load_cfg called).
723
724 Button delete_custom invokes delete_custom() to delete
725 a custom theme from idleConf.userCfg['highlight'] and changes.
726 Button save_custom invokes save_as_new_theme() which calls
727 get_new_theme_name() and create_new() to save a custom theme
728 and its colors to idleConf.userCfg['highlight'].
729
730 Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
731 if the current selected color for a tag is for the foreground or
732 background.
733
734 DynOptionMenu targetlist contains a readable description of the
735 tags applied to Python source within IDLE. Selecting one of the
736 tags from this list populates highlight_target, which has a callback
737 function set_highlight_target().
738
739 Text widget highlight_sample displays a block of text (which is
740 mock Python code) in which is embedded the defined tags and reflects
741 the color attributes of the current theme and changes for those tags.
742 Mouse button 1 allows for selection of a tag and updates
743 highlight_target with that tag value.
744
745 Note: The font in highlight_sample is set through the config in
746 the fonts tab.
747
748 In other words, a tag can be selected either from targetlist or
749 by clicking on the sample text within highlight_sample. The
750 plane (foreground/background) is selected via the radiobutton.
751 Together, these two (tag and plane) control what color is
752 shown in set_color_sample() for the current theme. Button set_color
753 invokes get_color() which displays a ColorChooser to change the
754 color for the selected tag/plane. If a new color is picked,
755 it will be saved to changes and the highlight_sample and
756 frame background will be updated.
757
758 Tk Variables:
759 color: Color of selected target.
760 builtin_name: Menu variable for built-in theme.
761 custom_name: Menu variable for custom theme.
762 fg_bg_toggle: Toggle for foreground/background color.
763 Note: this has no callback.
764 theme_source: Selector for built-in or custom theme.
765 highlight_target: Menu variable for the highlight tag target.
766
767 Instance Data Attributes:
768 theme_elements: Dictionary of tags for text highlighting.
769 The key is the display name and the value is a tuple of
770 (tag name, display sort order).
771
772 Methods [attachment]:
773 load_theme_cfg: Load current highlight colors.
774 get_color: Invoke colorchooser [button_set_color].
775 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
776 set_highlight_target: set fg_bg_toggle, set_color_sample().
777 set_color_sample: Set frame background to target.
778 on_new_color_set: Set new color and add option.
779 paint_theme_sample: Recolor sample.
780 get_new_theme_name: Get from popup.
781 create_new: Combine theme with changes and save.
782 save_as_new_theme: Save [button_save_custom].
783 set_theme_type: Command for [theme_source].
784 delete_custom: Activate default [button_delete_custom].
785 save_new: Save to userCfg['theme'] (is function).
786
787 Widgets of highlights page frame: (*) widgets bound to self
788 frame_custom: LabelFrame
789 (*)highlight_sample: Text
790 (*)frame_color_set: Frame
791 (*)button_set_color: Button
792 (*)targetlist: DynOptionMenu - highlight_target
793 frame_fg_bg_toggle: Frame
794 (*)fg_on: Radiobutton - fg_bg_toggle
795 (*)bg_on: Radiobutton - fg_bg_toggle
796 (*)button_save_custom: Button
797 frame_theme: LabelFrame
798 theme_type_title: Label
799 (*)builtin_theme_on: Radiobutton - theme_source
800 (*)custom_theme_on: Radiobutton - theme_source
801 (*)builtinlist: DynOptionMenu - builtin_name
802 (*)customlist: DynOptionMenu - custom_name
803 (*)button_delete_custom: Button
804 (*)theme_message: Label
805 """
806 self.theme_elements = {
807 'Normal Text': ('normal', '00'),
Cheryl Sabellade651622018-06-01 21:45:54 -0400808 'Code Context': ('context', '01'),
809 'Python Keywords': ('keyword', '02'),
810 'Python Definitions': ('definition', '03'),
811 'Python Builtins': ('builtin', '04'),
812 'Python Comments': ('comment', '05'),
813 'Python Strings': ('string', '06'),
814 'Selected Text': ('hilite', '07'),
815 'Found Text': ('hit', '08'),
816 'Cursor': ('cursor', '09'),
817 'Editor Breakpoint': ('break', '10'),
818 'Shell Normal Text': ('console', '11'),
819 'Shell Error Text': ('error', '12'),
820 'Shell Stdout Text': ('stdout', '13'),
821 'Shell Stderr Text': ('stderr', '14'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400822 }
823 self.builtin_name = tracers.add(
824 StringVar(self), self.var_changed_builtin_name)
825 self.custom_name = tracers.add(
826 StringVar(self), self.var_changed_custom_name)
827 self.fg_bg_toggle = BooleanVar(self)
828 self.color = tracers.add(
829 StringVar(self), self.var_changed_color)
830 self.theme_source = tracers.add(
831 BooleanVar(self), self.var_changed_theme_source)
832 self.highlight_target = tracers.add(
833 StringVar(self), self.var_changed_highlight_target)
834
835 # Create widgets:
836 # body frame and section frames.
837 frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
838 text=' Custom Highlighting ')
839 frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
840 text=' Highlighting Theme ')
841 # frame_custom.
842 text = self.highlight_sample = Text(
843 frame_custom, relief=SOLID, borderwidth=1,
844 font=('courier', 12, ''), cursor='hand2', width=21, height=13,
845 takefocus=FALSE, highlightthickness=0, wrap=NONE)
846 text.bind('<Double-Button-1>', lambda e: 'break')
847 text.bind('<B1-Motion>', lambda e: 'break')
wohlganger58fc71c2017-09-10 16:19:47 -0500848 text_and_tags=(
849 ('\n', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400850 ('#you can click here', 'comment'), ('\n', 'normal'),
851 ('#to choose items', 'comment'), ('\n', 'normal'),
Cheryl Sabellade651622018-06-01 21:45:54 -0400852 ('code context section', 'context'), ('\n\n', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400853 ('def', 'keyword'), (' ', 'normal'),
854 ('func', 'definition'), ('(param):\n ', 'normal'),
855 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
856 ("'string'", 'string'), ('\n var1 = ', 'normal'),
857 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
858 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
859 ('list', 'builtin'), ('(', 'normal'),
860 ('None', 'keyword'), (')\n', 'normal'),
861 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
862 (' error ', 'error'), (' ', 'normal'),
863 ('cursor |', 'cursor'), ('\n ', 'normal'),
864 ('shell', 'console'), (' ', 'normal'),
865 ('stdout', 'stdout'), (' ', 'normal'),
866 ('stderr', 'stderr'), ('\n\n', 'normal'))
867 for texttag in text_and_tags:
868 text.insert(END, texttag[0], texttag[1])
869 for element in self.theme_elements:
870 def tem(event, elem=element):
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400871 # event.widget.winfo_top_level().highlight_target.set(elem)
872 self.highlight_target.set(elem)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400873 text.tag_bind(
874 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400875 text['state'] = 'disabled'
876 self.style.configure('frame_color_set.TFrame', borderwidth=1,
877 relief='solid')
878 self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400879 frame_fg_bg_toggle = Frame(frame_custom)
880 self.button_set_color = Button(
881 self.frame_color_set, text='Choose Color for :',
Cheryl Sabella7028e592017-08-26 14:26:02 -0400882 command=self.get_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400883 self.targetlist = DynOptionMenu(
884 self.frame_color_set, self.highlight_target, None,
885 highlightthickness=0) #, command=self.set_highlight_targetBinding
886 self.fg_on = Radiobutton(
887 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
888 text='Foreground', command=self.set_color_sample_binding)
889 self.bg_on = Radiobutton(
890 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
891 text='Background', command=self.set_color_sample_binding)
892 self.fg_bg_toggle.set(1)
893 self.button_save_custom = Button(
894 frame_custom, text='Save as New Custom Theme',
895 command=self.save_as_new_theme)
896 # frame_theme.
897 theme_type_title = Label(frame_theme, text='Select : ')
898 self.builtin_theme_on = Radiobutton(
899 frame_theme, variable=self.theme_source, value=1,
900 command=self.set_theme_type, text='a Built-in Theme')
901 self.custom_theme_on = Radiobutton(
902 frame_theme, variable=self.theme_source, value=0,
903 command=self.set_theme_type, text='a Custom Theme')
904 self.builtinlist = DynOptionMenu(
905 frame_theme, self.builtin_name, None, command=None)
906 self.customlist = DynOptionMenu(
907 frame_theme, self.custom_name, None, command=None)
908 self.button_delete_custom = Button(
909 frame_theme, text='Delete Custom Theme',
910 command=self.delete_custom)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400911 self.theme_message = Label(frame_theme, borderwidth=2)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400912 # Pack widgets:
913 # body.
914 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
wohlganger58fc71c2017-09-10 16:19:47 -0500915 frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400916 # frame_custom.
917 self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
918 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
919 self.highlight_sample.pack(
920 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
921 self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
922 self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
923 self.fg_on.pack(side=LEFT, anchor=E)
924 self.bg_on.pack(side=RIGHT, anchor=W)
925 self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
926 # frame_theme.
927 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
928 self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
929 self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
930 self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
931 self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
932 self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
933 self.theme_message.pack(side=TOP, fill=X, pady=5)
934
935 def load_theme_cfg(self):
936 """Load current configuration settings for the theme options.
937
938 Based on the theme_source toggle, the theme is set as
939 either builtin or custom and the initial widget values
940 reflect the current settings from idleConf.
941
942 Attributes updated:
943 theme_source: Set from idleConf.
944 builtinlist: List of default themes from idleConf.
945 customlist: List of custom themes from idleConf.
946 custom_theme_on: Disabled if there are no custom themes.
947 custom_theme: Message with additional information.
948 targetlist: Create menu from self.theme_elements.
949
950 Methods:
951 set_theme_type
952 paint_theme_sample
953 set_highlight_target
954 """
955 # Set current theme type radiobutton.
956 self.theme_source.set(idleConf.GetOption(
957 'main', 'Theme', 'default', type='bool', default=1))
958 # Set current theme.
959 current_option = idleConf.CurrentTheme()
960 # Load available theme option menus.
961 if self.theme_source.get(): # Default theme selected.
962 item_list = idleConf.GetSectionList('default', 'highlight')
963 item_list.sort()
964 self.builtinlist.SetMenu(item_list, current_option)
965 item_list = idleConf.GetSectionList('user', 'highlight')
966 item_list.sort()
967 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400968 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400969 self.custom_name.set('- no custom themes -')
970 else:
971 self.customlist.SetMenu(item_list, item_list[0])
972 else: # User theme selected.
973 item_list = idleConf.GetSectionList('user', 'highlight')
974 item_list.sort()
975 self.customlist.SetMenu(item_list, current_option)
976 item_list = idleConf.GetSectionList('default', 'highlight')
977 item_list.sort()
978 self.builtinlist.SetMenu(item_list, item_list[0])
979 self.set_theme_type()
980 # Load theme element option menu.
981 theme_names = list(self.theme_elements.keys())
982 theme_names.sort(key=lambda x: self.theme_elements[x][1])
983 self.targetlist.SetMenu(theme_names, theme_names[0])
984 self.paint_theme_sample()
985 self.set_highlight_target()
986
987 def var_changed_builtin_name(self, *params):
988 """Process new builtin theme selection.
989
990 Add the changed theme's name to the changed_items and recreate
991 the sample with the values from the selected theme.
992 """
993 old_themes = ('IDLE Classic', 'IDLE New')
994 value = self.builtin_name.get()
995 if value not in old_themes:
996 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
997 changes.add_option('main', 'Theme', 'name', old_themes[0])
998 changes.add_option('main', 'Theme', 'name2', value)
999 self.theme_message['text'] = 'New theme, see Help'
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001000 else:
1001 changes.add_option('main', 'Theme', 'name', value)
1002 changes.add_option('main', 'Theme', 'name2', '')
1003 self.theme_message['text'] = ''
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001004 self.paint_theme_sample()
1005
1006 def var_changed_custom_name(self, *params):
1007 """Process new custom theme selection.
1008
1009 If a new custom theme is selected, add the name to the
1010 changed_items and apply the theme to the sample.
1011 """
1012 value = self.custom_name.get()
1013 if value != '- no custom themes -':
1014 changes.add_option('main', 'Theme', 'name', value)
1015 self.paint_theme_sample()
1016
1017 def var_changed_theme_source(self, *params):
1018 """Process toggle between builtin and custom theme.
1019
1020 Update the default toggle value and apply the newly
1021 selected theme type.
1022 """
1023 value = self.theme_source.get()
1024 changes.add_option('main', 'Theme', 'default', value)
1025 if value:
1026 self.var_changed_builtin_name()
1027 else:
1028 self.var_changed_custom_name()
1029
1030 def var_changed_color(self, *params):
1031 "Process change to color choice."
1032 self.on_new_color_set()
1033
1034 def var_changed_highlight_target(self, *params):
1035 "Process selection of new target tag for highlighting."
1036 self.set_highlight_target()
1037
1038 def set_theme_type(self):
1039 """Set available screen options based on builtin or custom theme.
1040
1041 Attributes accessed:
1042 theme_source
1043
1044 Attributes updated:
1045 builtinlist
1046 customlist
1047 button_delete_custom
1048 custom_theme_on
1049
1050 Called from:
1051 handler for builtin_theme_on and custom_theme_on
1052 delete_custom
1053 create_new
1054 load_theme_cfg
1055 """
1056 if self.theme_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -04001057 self.builtinlist['state'] = 'normal'
1058 self.customlist['state'] = 'disabled'
1059 self.button_delete_custom.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001060 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001061 self.builtinlist['state'] = 'disabled'
1062 self.custom_theme_on.state(('!disabled',))
1063 self.customlist['state'] = 'normal'
1064 self.button_delete_custom.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001065
1066 def get_color(self):
1067 """Handle button to select a new color for the target tag.
1068
1069 If a new color is selected while using a builtin theme, a
1070 name must be supplied to create a custom theme.
1071
1072 Attributes accessed:
1073 highlight_target
1074 frame_color_set
1075 theme_source
1076
1077 Attributes updated:
1078 color
1079
1080 Methods:
1081 get_new_theme_name
1082 create_new
1083 """
1084 target = self.highlight_target.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -04001085 prev_color = self.style.lookup(self.frame_color_set['style'],
1086 'background')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001087 rgbTuplet, color_string = tkColorChooser.askcolor(
1088 parent=self, title='Pick new color for : '+target,
1089 initialcolor=prev_color)
1090 if color_string and (color_string != prev_color):
1091 # User didn't cancel and they chose a new color.
1092 if self.theme_source.get(): # Current theme is a built-in.
1093 message = ('Your changes will be saved as a new Custom Theme. '
1094 'Enter a name for your new Custom Theme below.')
1095 new_theme = self.get_new_theme_name(message)
1096 if not new_theme: # User cancelled custom theme creation.
1097 return
1098 else: # Create new custom theme based on previously active theme.
1099 self.create_new(new_theme)
1100 self.color.set(color_string)
1101 else: # Current theme is user defined.
1102 self.color.set(color_string)
1103
1104 def on_new_color_set(self):
1105 "Display sample of new color selection on the dialog."
1106 new_color = self.color.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -04001107 self.style.configure('frame_color_set.TFrame', background=new_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001108 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1109 sample_element = self.theme_elements[self.highlight_target.get()][0]
1110 self.highlight_sample.tag_config(sample_element, **{plane: new_color})
1111 theme = self.custom_name.get()
1112 theme_element = sample_element + '-' + plane
1113 changes.add_option('highlight', theme, theme_element, new_color)
1114
1115 def get_new_theme_name(self, message):
1116 "Return name of new theme from query popup."
1117 used_names = (idleConf.GetSectionList('user', 'highlight') +
1118 idleConf.GetSectionList('default', 'highlight'))
1119 new_theme = SectionName(
1120 self, 'New Custom Theme', message, used_names).result
1121 return new_theme
1122
1123 def save_as_new_theme(self):
1124 """Prompt for new theme name and create the theme.
1125
1126 Methods:
1127 get_new_theme_name
1128 create_new
1129 """
1130 new_theme_name = self.get_new_theme_name('New Theme Name:')
1131 if new_theme_name:
1132 self.create_new(new_theme_name)
1133
1134 def create_new(self, new_theme_name):
1135 """Create a new custom theme with the given name.
1136
1137 Create the new theme based on the previously active theme
1138 with the current changes applied. Once it is saved, then
1139 activate the new theme.
1140
1141 Attributes accessed:
1142 builtin_name
1143 custom_name
1144
1145 Attributes updated:
1146 customlist
1147 theme_source
1148
1149 Method:
1150 save_new
1151 set_theme_type
1152 """
1153 if self.theme_source.get():
1154 theme_type = 'default'
1155 theme_name = self.builtin_name.get()
1156 else:
1157 theme_type = 'user'
1158 theme_name = self.custom_name.get()
1159 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
1160 # Apply any of the old theme's unsaved changes to the new theme.
1161 if theme_name in changes['highlight']:
1162 theme_changes = changes['highlight'][theme_name]
1163 for element in theme_changes:
1164 new_theme[element] = theme_changes[element]
1165 # Save the new theme.
1166 self.save_new(new_theme_name, new_theme)
1167 # Change GUI over to the new theme.
1168 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
1169 custom_theme_list.sort()
1170 self.customlist.SetMenu(custom_theme_list, new_theme_name)
1171 self.theme_source.set(0)
1172 self.set_theme_type()
1173
1174 def set_highlight_target(self):
1175 """Set fg/bg toggle and color based on highlight tag target.
1176
1177 Instance variables accessed:
1178 highlight_target
1179
1180 Attributes updated:
1181 fg_on
1182 bg_on
1183 fg_bg_toggle
1184
1185 Methods:
1186 set_color_sample
1187
1188 Called from:
1189 var_changed_highlight_target
1190 load_theme_cfg
1191 """
1192 if self.highlight_target.get() == 'Cursor': # bg not possible
Cheryl Sabella7028e592017-08-26 14:26:02 -04001193 self.fg_on.state(('disabled',))
1194 self.bg_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001195 self.fg_bg_toggle.set(1)
1196 else: # Both fg and bg can be set.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001197 self.fg_on.state(('!disabled',))
1198 self.bg_on.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001199 self.fg_bg_toggle.set(1)
1200 self.set_color_sample()
1201
1202 def set_color_sample_binding(self, *args):
1203 """Change color sample based on foreground/background toggle.
1204
1205 Methods:
1206 set_color_sample
1207 """
1208 self.set_color_sample()
1209
1210 def set_color_sample(self):
1211 """Set the color of the frame background to reflect the selected target.
1212
1213 Instance variables accessed:
1214 theme_elements
1215 highlight_target
1216 fg_bg_toggle
1217 highlight_sample
1218
1219 Attributes updated:
1220 frame_color_set
1221 """
1222 # Set the color sample area.
1223 tag = self.theme_elements[self.highlight_target.get()][0]
1224 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1225 color = self.highlight_sample.tag_cget(tag, plane)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001226 self.style.configure('frame_color_set.TFrame', background=color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001227
1228 def paint_theme_sample(self):
1229 """Apply the theme colors to each element tag in the sample text.
1230
1231 Instance attributes accessed:
1232 theme_elements
1233 theme_source
1234 builtin_name
1235 custom_name
1236
1237 Attributes updated:
1238 highlight_sample: Set the tag elements to the theme.
1239
1240 Methods:
1241 set_color_sample
1242
1243 Called from:
1244 var_changed_builtin_name
1245 var_changed_custom_name
1246 load_theme_cfg
1247 """
1248 if self.theme_source.get(): # Default theme
1249 theme = self.builtin_name.get()
1250 else: # User theme
1251 theme = self.custom_name.get()
1252 for element_title in self.theme_elements:
1253 element = self.theme_elements[element_title][0]
1254 colors = idleConf.GetHighlight(theme, element)
1255 if element == 'cursor': # Cursor sample needs special painting.
1256 colors['background'] = idleConf.GetHighlight(
Terry Jan Reedyc1419572019-03-22 18:23:41 -04001257 theme, 'normal')['background']
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001258 # Handle any unsaved changes to this theme.
1259 if theme in changes['highlight']:
1260 theme_dict = changes['highlight'][theme]
1261 if element + '-foreground' in theme_dict:
1262 colors['foreground'] = theme_dict[element + '-foreground']
1263 if element + '-background' in theme_dict:
1264 colors['background'] = theme_dict[element + '-background']
1265 self.highlight_sample.tag_config(element, **colors)
1266 self.set_color_sample()
1267
1268 def save_new(self, theme_name, theme):
1269 """Save a newly created theme to idleConf.
1270
1271 theme_name - string, the name of the new theme
1272 theme - dictionary containing the new theme
1273 """
1274 if not idleConf.userCfg['highlight'].has_section(theme_name):
1275 idleConf.userCfg['highlight'].add_section(theme_name)
1276 for element in theme:
1277 value = theme[element]
1278 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
1279
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001280 def askyesno(self, *args, **kwargs):
1281 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001282 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001283
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001284 def delete_custom(self):
1285 """Handle event to delete custom theme.
1286
1287 The current theme is deactivated and the default theme is
1288 activated. The custom theme is permanently removed from
1289 the config file.
1290
1291 Attributes accessed:
1292 custom_name
1293
1294 Attributes updated:
1295 custom_theme_on
1296 customlist
1297 theme_source
1298 builtin_name
1299
1300 Methods:
1301 deactivate_current_config
1302 save_all_changed_extensions
1303 activate_config_changes
1304 set_theme_type
1305 """
1306 theme_name = self.custom_name.get()
1307 delmsg = 'Are you sure you wish to delete the theme %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001308 if not self.askyesno(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001309 'Delete Theme', delmsg % theme_name, parent=self):
1310 return
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001311 self.cd.deactivate_current_config()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001312 # Remove theme from changes, config, and file.
1313 changes.delete_section('highlight', theme_name)
1314 # Reload user theme list.
1315 item_list = idleConf.GetSectionList('user', 'highlight')
1316 item_list.sort()
1317 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001318 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001319 self.customlist.SetMenu(item_list, '- no custom themes -')
1320 else:
1321 self.customlist.SetMenu(item_list, item_list[0])
1322 # Revert to default theme.
1323 self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
1324 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
1325 # User can't back out of these changes, they must be applied now.
1326 changes.save_all()
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001327 self.cd.save_all_changed_extensions()
1328 self.cd.activate_config_changes()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001329 self.set_theme_type()
1330
1331
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001332class KeysPage(Frame):
1333
1334 def __init__(self, master):
1335 super().__init__(master)
1336 self.cd = master.master
1337 self.create_page_keys()
1338 self.load_key_cfg()
1339
1340 def create_page_keys(self):
1341 """Return frame of widgets for Keys tab.
1342
1343 Enable users to provisionally change both individual and sets of
1344 keybindings (shortcut keys). Except for features implemented as
1345 extensions, keybindings are stored in complete sets called
1346 keysets. Built-in keysets in idlelib/config-keys.def are fixed
1347 as far as the dialog is concerned. Any keyset can be used as the
1348 base for a new custom keyset, stored in .idlerc/config-keys.cfg.
1349
1350 Function load_key_cfg() initializes tk variables and keyset
1351 lists and calls load_keys_list for the current keyset.
1352 Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
1353 keyset_source, which controls if the current set of keybindings
1354 are from a builtin or custom keyset. DynOptionMenus builtinlist
1355 and customlist contain lists of the builtin and custom keysets,
1356 respectively, and the current item from each list is stored in
1357 vars builtin_name and custom_name.
1358
1359 Button delete_custom_keys invokes delete_custom_keys() to delete
1360 a custom keyset from idleConf.userCfg['keys'] and changes. Button
1361 save_custom_keys invokes save_as_new_key_set() which calls
1362 get_new_keys_name() and create_new_key_set() to save a custom keyset
1363 and its keybindings to idleConf.userCfg['keys'].
1364
1365 Listbox bindingslist contains all of the keybindings for the
1366 selected keyset. The keybindings are loaded in load_keys_list()
1367 and are pairs of (event, [keys]) where keys can be a list
1368 of one or more key combinations to bind to the same event.
1369 Mouse button 1 click invokes on_bindingslist_select(), which
1370 allows button_new_keys to be clicked.
1371
1372 So, an item is selected in listbindings, which activates
1373 button_new_keys, and clicking button_new_keys calls function
1374 get_new_keys(). Function get_new_keys() gets the key mappings from the
1375 current keyset for the binding event item that was selected. The
1376 function then displays another dialog, GetKeysDialog, with the
Cheryl Sabella82aff622017-08-17 20:39:00 -04001377 selected binding event and current keys and allows new key sequences
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001378 to be entered for that binding event. If the keys aren't
1379 changed, nothing happens. If the keys are changed and the keyset
1380 is a builtin, function get_new_keys_name() will be called
1381 for input of a custom keyset name. If no name is given, then the
1382 change to the keybinding will abort and no updates will be made. If
1383 a custom name is entered in the prompt or if the current keyset was
1384 already custom (and thus didn't require a prompt), then
1385 idleConf.userCfg['keys'] is updated in function create_new_key_set()
1386 with the change to the event binding. The item listing in bindingslist
1387 is updated with the new keys. Var keybinding is also set which invokes
1388 the callback function, var_changed_keybinding, to add the change to
1389 the 'keys' or 'extensions' changes tracker based on the binding type.
1390
1391 Tk Variables:
1392 keybinding: Action/key bindings.
1393
1394 Methods:
1395 load_keys_list: Reload active set.
1396 create_new_key_set: Combine active keyset and changes.
1397 set_keys_type: Command for keyset_source.
1398 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1399 deactivate_current_config: Remove keys bindings in editors.
1400
1401 Widgets for KeysPage(frame): (*) widgets bound to self
1402 frame_key_sets: LabelFrame
1403 frames[0]: Frame
1404 (*)builtin_keyset_on: Radiobutton - var keyset_source
1405 (*)custom_keyset_on: Radiobutton - var keyset_source
1406 (*)builtinlist: DynOptionMenu - var builtin_name,
1407 func keybinding_selected
1408 (*)customlist: DynOptionMenu - var custom_name,
1409 func keybinding_selected
1410 (*)keys_message: Label
1411 frames[1]: Frame
1412 (*)button_delete_custom_keys: Button - delete_custom_keys
1413 (*)button_save_custom_keys: Button - save_as_new_key_set
1414 frame_custom: LabelFrame
1415 frame_target: Frame
1416 target_title: Label
1417 scroll_target_y: Scrollbar
1418 scroll_target_x: Scrollbar
1419 (*)bindingslist: ListBox - on_bindingslist_select
1420 (*)button_new_keys: Button - get_new_keys & ..._name
1421 """
1422 self.builtin_name = tracers.add(
1423 StringVar(self), self.var_changed_builtin_name)
1424 self.custom_name = tracers.add(
1425 StringVar(self), self.var_changed_custom_name)
1426 self.keyset_source = tracers.add(
1427 BooleanVar(self), self.var_changed_keyset_source)
1428 self.keybinding = tracers.add(
1429 StringVar(self), self.var_changed_keybinding)
1430
1431 # Create widgets:
1432 # body and section frames.
1433 frame_custom = LabelFrame(
1434 self, borderwidth=2, relief=GROOVE,
1435 text=' Custom Key Bindings ')
1436 frame_key_sets = LabelFrame(
1437 self, borderwidth=2, relief=GROOVE, text=' Key Set ')
1438 # frame_custom.
1439 frame_target = Frame(frame_custom)
1440 target_title = Label(frame_target, text='Action - Key(s)')
1441 scroll_target_y = Scrollbar(frame_target)
1442 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1443 self.bindingslist = Listbox(
1444 frame_target, takefocus=FALSE, exportselection=FALSE)
1445 self.bindingslist.bind('<ButtonRelease-1>',
1446 self.on_bindingslist_select)
1447 scroll_target_y['command'] = self.bindingslist.yview
1448 scroll_target_x['command'] = self.bindingslist.xview
1449 self.bindingslist['yscrollcommand'] = scroll_target_y.set
1450 self.bindingslist['xscrollcommand'] = scroll_target_x.set
1451 self.button_new_keys = Button(
1452 frame_custom, text='Get New Keys for Selection',
Terry Jan Reedye8f7c782017-11-28 21:52:32 -05001453 command=self.get_new_keys, state='disabled')
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001454 # frame_key_sets.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001455 frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001456 for i in range(2)]
1457 self.builtin_keyset_on = Radiobutton(
1458 frames[0], variable=self.keyset_source, value=1,
1459 command=self.set_keys_type, text='Use a Built-in Key Set')
1460 self.custom_keyset_on = Radiobutton(
1461 frames[0], variable=self.keyset_source, value=0,
1462 command=self.set_keys_type, text='Use a Custom Key Set')
1463 self.builtinlist = DynOptionMenu(
1464 frames[0], self.builtin_name, None, command=None)
1465 self.customlist = DynOptionMenu(
1466 frames[0], self.custom_name, None, command=None)
1467 self.button_delete_custom_keys = Button(
1468 frames[1], text='Delete Custom Key Set',
1469 command=self.delete_custom_keys)
1470 self.button_save_custom_keys = Button(
1471 frames[1], text='Save as New Custom Key Set',
1472 command=self.save_as_new_key_set)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001473 self.keys_message = Label(frames[0], borderwidth=2)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001474
1475 # Pack widgets:
1476 # body.
1477 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1478 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1479 # frame_custom.
1480 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1481 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
1482 # frame_target.
1483 frame_target.columnconfigure(0, weight=1)
1484 frame_target.rowconfigure(1, weight=1)
1485 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1486 self.bindingslist.grid(row=1, column=0, sticky=NSEW)
1487 scroll_target_y.grid(row=1, column=1, sticky=NS)
1488 scroll_target_x.grid(row=2, column=0, sticky=EW)
1489 # frame_key_sets.
1490 self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
1491 self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
1492 self.builtinlist.grid(row=0, column=1, sticky=NSEW)
1493 self.customlist.grid(row=1, column=1, sticky=NSEW)
1494 self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
1495 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1496 self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1497 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1498 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
1499
1500 def load_key_cfg(self):
1501 "Load current configuration settings for the keybinding options."
1502 # Set current keys type radiobutton.
1503 self.keyset_source.set(idleConf.GetOption(
1504 'main', 'Keys', 'default', type='bool', default=1))
1505 # Set current keys.
1506 current_option = idleConf.CurrentKeys()
1507 # Load available keyset option menus.
1508 if self.keyset_source.get(): # Default theme selected.
1509 item_list = idleConf.GetSectionList('default', 'keys')
1510 item_list.sort()
1511 self.builtinlist.SetMenu(item_list, current_option)
1512 item_list = idleConf.GetSectionList('user', 'keys')
1513 item_list.sort()
1514 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001515 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001516 self.custom_name.set('- no custom keys -')
1517 else:
1518 self.customlist.SetMenu(item_list, item_list[0])
1519 else: # User key set selected.
1520 item_list = idleConf.GetSectionList('user', 'keys')
1521 item_list.sort()
1522 self.customlist.SetMenu(item_list, current_option)
1523 item_list = idleConf.GetSectionList('default', 'keys')
1524 item_list.sort()
1525 self.builtinlist.SetMenu(item_list, idleConf.default_keys())
1526 self.set_keys_type()
1527 # Load keyset element list.
1528 keyset_name = idleConf.CurrentKeys()
1529 self.load_keys_list(keyset_name)
1530
1531 def var_changed_builtin_name(self, *params):
1532 "Process selection of builtin key set."
1533 old_keys = (
1534 'IDLE Classic Windows',
1535 'IDLE Classic Unix',
1536 'IDLE Classic Mac',
1537 'IDLE Classic OSX',
1538 )
1539 value = self.builtin_name.get()
1540 if value not in old_keys:
1541 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1542 changes.add_option('main', 'Keys', 'name', old_keys[0])
1543 changes.add_option('main', 'Keys', 'name2', value)
1544 self.keys_message['text'] = 'New key set, see Help'
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001545 else:
1546 changes.add_option('main', 'Keys', 'name', value)
1547 changes.add_option('main', 'Keys', 'name2', '')
1548 self.keys_message['text'] = ''
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001549 self.load_keys_list(value)
1550
1551 def var_changed_custom_name(self, *params):
1552 "Process selection of custom key set."
1553 value = self.custom_name.get()
1554 if value != '- no custom keys -':
1555 changes.add_option('main', 'Keys', 'name', value)
1556 self.load_keys_list(value)
1557
1558 def var_changed_keyset_source(self, *params):
1559 "Process toggle between builtin key set and custom key set."
1560 value = self.keyset_source.get()
1561 changes.add_option('main', 'Keys', 'default', value)
1562 if value:
1563 self.var_changed_builtin_name()
1564 else:
1565 self.var_changed_custom_name()
1566
1567 def var_changed_keybinding(self, *params):
1568 "Store change to a keybinding."
1569 value = self.keybinding.get()
1570 key_set = self.custom_name.get()
1571 event = self.bindingslist.get(ANCHOR).split()[0]
1572 if idleConf.IsCoreBinding(event):
1573 changes.add_option('keys', key_set, event, value)
1574 else: # Event is an extension binding.
1575 ext_name = idleConf.GetExtnNameForEvent(event)
1576 ext_keybind_section = ext_name + '_cfgBindings'
1577 changes.add_option('extensions', ext_keybind_section, event, value)
1578
1579 def set_keys_type(self):
1580 "Set available screen options based on builtin or custom key set."
1581 if self.keyset_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -04001582 self.builtinlist['state'] = 'normal'
1583 self.customlist['state'] = 'disabled'
1584 self.button_delete_custom_keys.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001585 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001586 self.builtinlist['state'] = 'disabled'
1587 self.custom_keyset_on.state(('!disabled',))
1588 self.customlist['state'] = 'normal'
1589 self.button_delete_custom_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001590
1591 def get_new_keys(self):
1592 """Handle event to change key binding for selected line.
1593
1594 A selection of a key/binding in the list of current
1595 bindings pops up a dialog to enter a new binding. If
1596 the current key set is builtin and a binding has
1597 changed, then a name for a custom key set needs to be
1598 entered for the change to be applied.
1599 """
1600 list_index = self.bindingslist.index(ANCHOR)
1601 binding = self.bindingslist.get(list_index)
1602 bind_name = binding.split()[0]
1603 if self.keyset_source.get():
1604 current_key_set_name = self.builtin_name.get()
1605 else:
1606 current_key_set_name = self.custom_name.get()
1607 current_bindings = idleConf.GetCurrentKeySet()
1608 if current_key_set_name in changes['keys']: # unsaved changes
1609 key_set_changes = changes['keys'][current_key_set_name]
1610 for event in key_set_changes:
1611 current_bindings[event] = key_set_changes[event].split()
1612 current_key_sequences = list(current_bindings.values())
1613 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1614 current_key_sequences).result
1615 if new_keys:
1616 if self.keyset_source.get(): # Current key set is a built-in.
1617 message = ('Your changes will be saved as a new Custom Key Set.'
1618 ' Enter a name for your new Custom Key Set below.')
1619 new_keyset = self.get_new_keys_name(message)
1620 if not new_keyset: # User cancelled custom key set creation.
1621 self.bindingslist.select_set(list_index)
1622 self.bindingslist.select_anchor(list_index)
1623 return
1624 else: # Create new custom key set based on previously active key set.
1625 self.create_new_key_set(new_keyset)
1626 self.bindingslist.delete(list_index)
1627 self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
1628 self.bindingslist.select_set(list_index)
1629 self.bindingslist.select_anchor(list_index)
1630 self.keybinding.set(new_keys)
1631 else:
1632 self.bindingslist.select_set(list_index)
1633 self.bindingslist.select_anchor(list_index)
1634
1635 def get_new_keys_name(self, message):
1636 "Return new key set name from query popup."
1637 used_names = (idleConf.GetSectionList('user', 'keys') +
1638 idleConf.GetSectionList('default', 'keys'))
1639 new_keyset = SectionName(
1640 self, 'New Custom Key Set', message, used_names).result
1641 return new_keyset
1642
1643 def save_as_new_key_set(self):
1644 "Prompt for name of new key set and save changes using that name."
1645 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1646 if new_keys_name:
1647 self.create_new_key_set(new_keys_name)
1648
1649 def on_bindingslist_select(self, event):
1650 "Activate button to assign new keys to selected action."
Cheryl Sabella7028e592017-08-26 14:26:02 -04001651 self.button_new_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001652
1653 def create_new_key_set(self, new_key_set_name):
1654 """Create a new custom key set with the given name.
1655
1656 Copy the bindings/keys from the previously active keyset
1657 to the new keyset and activate the new custom keyset.
1658 """
1659 if self.keyset_source.get():
1660 prev_key_set_name = self.builtin_name.get()
1661 else:
1662 prev_key_set_name = self.custom_name.get()
1663 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1664 new_keys = {}
1665 for event in prev_keys: # Add key set to changed items.
1666 event_name = event[2:-2] # Trim off the angle brackets.
1667 binding = ' '.join(prev_keys[event])
1668 new_keys[event_name] = binding
1669 # Handle any unsaved changes to prev key set.
1670 if prev_key_set_name in changes['keys']:
1671 key_set_changes = changes['keys'][prev_key_set_name]
1672 for event in key_set_changes:
1673 new_keys[event] = key_set_changes[event]
1674 # Save the new key set.
1675 self.save_new_key_set(new_key_set_name, new_keys)
1676 # Change GUI over to the new key set.
1677 custom_key_list = idleConf.GetSectionList('user', 'keys')
1678 custom_key_list.sort()
1679 self.customlist.SetMenu(custom_key_list, new_key_set_name)
1680 self.keyset_source.set(0)
1681 self.set_keys_type()
1682
1683 def load_keys_list(self, keyset_name):
1684 """Reload the list of action/key binding pairs for the active key set.
1685
1686 An action/key binding can be selected to change the key binding.
1687 """
1688 reselect = False
1689 if self.bindingslist.curselection():
1690 reselect = True
1691 list_index = self.bindingslist.index(ANCHOR)
1692 keyset = idleConf.GetKeySet(keyset_name)
1693 bind_names = list(keyset.keys())
1694 bind_names.sort()
1695 self.bindingslist.delete(0, END)
1696 for bind_name in bind_names:
1697 key = ' '.join(keyset[bind_name])
1698 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1699 if keyset_name in changes['keys']:
1700 # Handle any unsaved changes to this key set.
1701 if bind_name in changes['keys'][keyset_name]:
1702 key = changes['keys'][keyset_name][bind_name]
1703 self.bindingslist.insert(END, bind_name+' - '+key)
1704 if reselect:
1705 self.bindingslist.see(list_index)
1706 self.bindingslist.select_set(list_index)
1707 self.bindingslist.select_anchor(list_index)
1708
1709 @staticmethod
1710 def save_new_key_set(keyset_name, keyset):
1711 """Save a newly created core key set.
1712
1713 Add keyset to idleConf.userCfg['keys'], not to disk.
1714 If the keyset doesn't exist, it is created. The
1715 binding/keys are taken from the keyset argument.
1716
1717 keyset_name - string, the name of the new key set
1718 keyset - dictionary containing the new keybindings
1719 """
1720 if not idleConf.userCfg['keys'].has_section(keyset_name):
1721 idleConf.userCfg['keys'].add_section(keyset_name)
1722 for event in keyset:
1723 value = keyset[event]
1724 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1725
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001726 def askyesno(self, *args, **kwargs):
1727 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001728 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001729
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001730 def delete_custom_keys(self):
1731 """Handle event to delete a custom key set.
1732
1733 Applying the delete deactivates the current configuration and
1734 reverts to the default. The custom key set is permanently
1735 deleted from the config file.
1736 """
1737 keyset_name = self.custom_name.get()
1738 delmsg = 'Are you sure you wish to delete the key set %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001739 if not self.askyesno(
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001740 'Delete Key Set', delmsg % keyset_name, parent=self):
1741 return
1742 self.cd.deactivate_current_config()
1743 # Remove key set from changes, config, and file.
1744 changes.delete_section('keys', keyset_name)
1745 # Reload user key set list.
1746 item_list = idleConf.GetSectionList('user', 'keys')
1747 item_list.sort()
1748 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001749 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001750 self.customlist.SetMenu(item_list, '- no custom keys -')
1751 else:
1752 self.customlist.SetMenu(item_list, item_list[0])
1753 # Revert to default key set.
1754 self.keyset_source.set(idleConf.defaultCfg['main']
Tal Einat604e7b92018-09-25 15:10:14 +03001755 .Get('Keys', 'default'))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001756 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Tal Einat604e7b92018-09-25 15:10:14 +03001757 or idleConf.default_keys())
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001758 # User can't back out of these changes, they must be applied now.
1759 changes.save_all()
1760 self.cd.save_all_changed_extensions()
1761 self.cd.activate_config_changes()
1762 self.set_keys_type()
1763
1764
csabellae8eb17b2017-07-30 18:39:17 -04001765class GenPage(Frame):
1766
csabella6f446be2017-08-01 00:24:07 -04001767 def __init__(self, master):
1768 super().__init__(master)
Tal Einat1ebee372019-07-23 13:02:11 +03001769
1770 self.init_validators()
csabellae8eb17b2017-07-30 18:39:17 -04001771 self.create_page_general()
1772 self.load_general_cfg()
1773
Tal Einat1ebee372019-07-23 13:02:11 +03001774 def init_validators(self):
1775 digits_or_empty_re = re.compile(r'[0-9]*')
1776 def is_digits_or_empty(s):
1777 "Return 's is blank or contains only digits'"
1778 return digits_or_empty_re.fullmatch(s) is not None
1779 self.digits_only = (self.register(is_digits_or_empty), '%P',)
1780
csabellae8eb17b2017-07-30 18:39:17 -04001781 def create_page_general(self):
1782 """Return frame of widgets for General tab.
1783
1784 Enable users to provisionally change general options. Function
1785 load_general_cfg intializes tk variables and helplist using
1786 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1787 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1788 set var autosave. Entry boxes win_width_int and win_height_int
1789 set var win_width and win_height. Setting var_name invokes the
1790 default callback that adds option to changes.
1791
1792 Helplist: load_general_cfg loads list user_helplist with
1793 name, position pairs and copies names to listbox helplist.
1794 Clicking a name invokes help_source selected. Clicking
1795 button_helplist_name invokes helplist_item_name, which also
1796 changes user_helplist. These functions all call
1797 set_add_delete_state. All but load call update_help_changes to
1798 rewrite changes['main']['HelpFiles'].
1799
Cheryl Sabella2f896462017-08-14 21:21:43 -04001800 Widgets for GenPage(Frame): (*) widgets bound to self
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001801 frame_window: LabelFrame
1802 frame_run: Frame
1803 startup_title: Label
1804 (*)startup_editor_on: Radiobutton - startup_edit
1805 (*)startup_shell_on: Radiobutton - startup_edit
1806 frame_win_size: Frame
1807 win_size_title: Label
1808 win_width_title: Label
1809 (*)win_width_int: Entry - win_width
1810 win_height_title: Label
1811 (*)win_height_int: Entry - win_height
Cheryl Sabella845d8642018-02-04 18:15:21 -05001812 frame_autocomplete: Frame
1813 auto_wait_title: Label
1814 (*)auto_wait_int: Entry - autocomplete_wait
1815 frame_paren1: Frame
1816 paren_style_title: Label
1817 (*)paren_style_type: OptionMenu - paren_style
1818 frame_paren2: Frame
1819 paren_time_title: Label
1820 (*)paren_flash_time: Entry - flash_delay
1821 (*)bell_on: Checkbutton - paren_bell
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001822 frame_editor: LabelFrame
1823 frame_save: Frame
1824 run_save_title: Label
1825 (*)save_ask_on: Radiobutton - autosave
1826 (*)save_auto_on: Radiobutton - autosave
Cheryl Sabella845d8642018-02-04 18:15:21 -05001827 frame_format: Frame
1828 format_width_title: Label
1829 (*)format_width_int: Entry - format_width
1830 frame_context: Frame
1831 context_title: Label
1832 (*)context_int: Entry - context_lines
Tal Einat604e7b92018-09-25 15:10:14 +03001833 frame_shell: LabelFrame
1834 frame_auto_squeeze_min_lines: Frame
1835 auto_squeeze_min_lines_title: Label
1836 (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
Cheryl Sabella2f896462017-08-14 21:21:43 -04001837 frame_help: LabelFrame
1838 frame_helplist: Frame
1839 frame_helplist_buttons: Frame
1840 (*)button_helplist_edit
1841 (*)button_helplist_add
1842 (*)button_helplist_remove
1843 (*)helplist: ListBox
1844 scroll_helplist: Scrollbar
csabellae8eb17b2017-07-30 18:39:17 -04001845 """
wohlganger58fc71c2017-09-10 16:19:47 -05001846 # Integer values need StringVar because int('') raises.
csabellae8eb17b2017-07-30 18:39:17 -04001847 self.startup_edit = tracers.add(
1848 IntVar(self), ('main', 'General', 'editor-on-startup'))
csabellae8eb17b2017-07-30 18:39:17 -04001849 self.win_width = tracers.add(
1850 StringVar(self), ('main', 'EditorWindow', 'width'))
1851 self.win_height = tracers.add(
1852 StringVar(self), ('main', 'EditorWindow', 'height'))
wohlganger58fc71c2017-09-10 16:19:47 -05001853 self.autocomplete_wait = tracers.add(
1854 StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
1855 self.paren_style = tracers.add(
1856 StringVar(self), ('extensions', 'ParenMatch', 'style'))
1857 self.flash_delay = tracers.add(
1858 StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
1859 self.paren_bell = tracers.add(
1860 BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
csabellae8eb17b2017-07-30 18:39:17 -04001861
Tal Einat604e7b92018-09-25 15:10:14 +03001862 self.auto_squeeze_min_lines = tracers.add(
1863 StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
1864
wohlganger58fc71c2017-09-10 16:19:47 -05001865 self.autosave = tracers.add(
1866 IntVar(self), ('main', 'General', 'autosave'))
1867 self.format_width = tracers.add(
1868 StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
1869 self.context_lines = tracers.add(
Cheryl Sabella29996a12018-06-01 19:23:00 -04001870 StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
wohlganger58fc71c2017-09-10 16:19:47 -05001871
1872 # Create widgets:
csabellae8eb17b2017-07-30 18:39:17 -04001873 # Section frames.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001874 frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
1875 text=' Window Preferences')
1876 frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
1877 text=' Editor Preferences')
Tal Einat604e7b92018-09-25 15:10:14 +03001878 frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
1879 text=' Shell Preferences')
csabellae8eb17b2017-07-30 18:39:17 -04001880 frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
Tal Einat604e7b92018-09-25 15:10:14 +03001881 text=' Additional Help Sources ')
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001882 # Frame_window.
1883 frame_run = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001884 startup_title = Label(frame_run, text='At Startup')
1885 self.startup_editor_on = Radiobutton(
1886 frame_run, variable=self.startup_edit, value=1,
1887 text="Open Edit Window")
1888 self.startup_shell_on = Radiobutton(
1889 frame_run, variable=self.startup_edit, value=0,
1890 text='Open Shell Window')
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001891
wohlganger58fc71c2017-09-10 16:19:47 -05001892 frame_win_size = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001893 win_size_title = Label(
1894 frame_win_size, text='Initial Window Size (in characters)')
1895 win_width_title = Label(frame_win_size, text='Width')
1896 self.win_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001897 frame_win_size, textvariable=self.win_width, width=3,
1898 validatecommand=self.digits_only, validate='key',
1899 )
csabellae8eb17b2017-07-30 18:39:17 -04001900 win_height_title = Label(frame_win_size, text='Height')
1901 self.win_height_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001902 frame_win_size, textvariable=self.win_height, width=3,
1903 validatecommand=self.digits_only, validate='key',
1904 )
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001905
wohlganger58fc71c2017-09-10 16:19:47 -05001906 frame_autocomplete = Frame(frame_window, borderwidth=0,)
1907 auto_wait_title = Label(frame_autocomplete,
1908 text='Completions Popup Wait (milliseconds)')
1909 self.auto_wait_int = Entry(frame_autocomplete, width=6,
Tal Einat1ebee372019-07-23 13:02:11 +03001910 textvariable=self.autocomplete_wait,
1911 validatecommand=self.digits_only,
1912 validate='key',
1913 )
wohlganger58fc71c2017-09-10 16:19:47 -05001914
1915 frame_paren1 = Frame(frame_window, borderwidth=0)
1916 paren_style_title = Label(frame_paren1, text='Paren Match Style')
1917 self.paren_style_type = OptionMenu(
1918 frame_paren1, self.paren_style, 'expression',
1919 "opener","parens","expression")
1920 frame_paren2 = Frame(frame_window, borderwidth=0)
1921 paren_time_title = Label(
1922 frame_paren2, text='Time Match Displayed (milliseconds)\n'
1923 '(0 is until next input)')
1924 self.paren_flash_time = Entry(
1925 frame_paren2, textvariable=self.flash_delay, width=6)
1926 self.bell_on = Checkbutton(
1927 frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
1928
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001929 # Frame_editor.
1930 frame_save = Frame(frame_editor, borderwidth=0)
1931 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
1932 self.save_ask_on = Radiobutton(
1933 frame_save, variable=self.autosave, value=0,
1934 text="Prompt to Save")
1935 self.save_auto_on = Radiobutton(
1936 frame_save, variable=self.autosave, value=1,
1937 text='No Prompt')
1938
wohlganger58fc71c2017-09-10 16:19:47 -05001939 frame_format = Frame(frame_editor, borderwidth=0)
1940 format_width_title = Label(frame_format,
1941 text='Format Paragraph Max Width')
1942 self.format_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001943 frame_format, textvariable=self.format_width, width=4,
1944 validatecommand=self.digits_only, validate='key',
1945 )
wohlganger58fc71c2017-09-10 16:19:47 -05001946
1947 frame_context = Frame(frame_editor, borderwidth=0)
Cheryl Sabella29996a12018-06-01 19:23:00 -04001948 context_title = Label(frame_context, text='Max Context Lines :')
wohlganger58fc71c2017-09-10 16:19:47 -05001949 self.context_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001950 frame_context, textvariable=self.context_lines, width=3,
1951 validatecommand=self.digits_only, validate='key',
1952 )
wohlganger58fc71c2017-09-10 16:19:47 -05001953
Tal Einat604e7b92018-09-25 15:10:14 +03001954 # Frame_shell.
1955 frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
1956 auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
1957 text='Auto-Squeeze Min. Lines:')
1958 self.auto_squeeze_min_lines_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001959 frame_auto_squeeze_min_lines, width=4,
1960 textvariable=self.auto_squeeze_min_lines,
1961 validatecommand=self.digits_only, validate='key',
1962 )
wohlganger58fc71c2017-09-10 16:19:47 -05001963
csabellae8eb17b2017-07-30 18:39:17 -04001964 # frame_help.
1965 frame_helplist = Frame(frame_help)
1966 frame_helplist_buttons = Frame(frame_helplist)
1967 self.helplist = Listbox(
1968 frame_helplist, height=5, takefocus=True,
1969 exportselection=FALSE)
1970 scroll_helplist = Scrollbar(frame_helplist)
1971 scroll_helplist['command'] = self.helplist.yview
1972 self.helplist['yscrollcommand'] = scroll_helplist.set
1973 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
1974 self.button_helplist_edit = Button(
Cheryl Sabella7028e592017-08-26 14:26:02 -04001975 frame_helplist_buttons, text='Edit', state='disabled',
csabellae8eb17b2017-07-30 18:39:17 -04001976 width=8, command=self.helplist_item_edit)
1977 self.button_helplist_add = Button(
1978 frame_helplist_buttons, text='Add',
1979 width=8, command=self.helplist_item_add)
1980 self.button_helplist_remove = Button(
Cheryl Sabella7028e592017-08-26 14:26:02 -04001981 frame_helplist_buttons, text='Remove', state='disabled',
csabellae8eb17b2017-07-30 18:39:17 -04001982 width=8, command=self.helplist_item_remove)
1983
1984 # Pack widgets:
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001985 # Body.
1986 frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1987 frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Tal Einat604e7b92018-09-25 15:10:14 +03001988 frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
csabellae8eb17b2017-07-30 18:39:17 -04001989 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1990 # frame_run.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001991 frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04001992 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1993 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1994 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
csabellae8eb17b2017-07-30 18:39:17 -04001995 # frame_win_size.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001996 frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04001997 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1998 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
1999 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
2000 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
2001 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05002002 # frame_autocomplete.
2003 frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
2004 auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2005 self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
2006 # frame_paren.
2007 frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
2008 paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2009 self.paren_style_type.pack(side=TOP, padx=10, pady=5)
2010 frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
2011 paren_time_title.pack(side=LEFT, anchor=W, padx=5)
2012 self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
2013 self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
2014
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04002015 # frame_save.
2016 frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
2017 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2018 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
2019 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05002020 # frame_format.
2021 frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
2022 format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2023 self.format_width_int.pack(side=TOP, padx=10, pady=5)
2024 # frame_context.
2025 frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
2026 context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2027 self.context_int.pack(side=TOP, padx=5, pady=5)
2028
Tal Einat604e7b92018-09-25 15:10:14 +03002029 # frame_auto_squeeze_min_lines
2030 frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
2031 auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
2032 self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
2033
csabellae8eb17b2017-07-30 18:39:17 -04002034 # frame_help.
2035 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
2036 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
2037 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
2038 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
2039 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
2040 self.button_helplist_add.pack(side=TOP, anchor=W)
2041 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
2042
2043 def load_general_cfg(self):
2044 "Load current configuration settings for the general options."
wohlganger58fc71c2017-09-10 16:19:47 -05002045 # Set variables for all windows.
csabellae8eb17b2017-07-30 18:39:17 -04002046 self.startup_edit.set(idleConf.GetOption(
wohlganger58fc71c2017-09-10 16:19:47 -05002047 'main', 'General', 'editor-on-startup', type='bool'))
csabellae8eb17b2017-07-30 18:39:17 -04002048 self.win_width.set(idleConf.GetOption(
2049 'main', 'EditorWindow', 'width', type='int'))
2050 self.win_height.set(idleConf.GetOption(
2051 'main', 'EditorWindow', 'height', type='int'))
wohlganger58fc71c2017-09-10 16:19:47 -05002052 self.autocomplete_wait.set(idleConf.GetOption(
2053 'extensions', 'AutoComplete', 'popupwait', type='int'))
2054 self.paren_style.set(idleConf.GetOption(
2055 'extensions', 'ParenMatch', 'style'))
2056 self.flash_delay.set(idleConf.GetOption(
2057 'extensions', 'ParenMatch', 'flash-delay', type='int'))
2058 self.paren_bell.set(idleConf.GetOption(
2059 'extensions', 'ParenMatch', 'bell'))
2060
2061 # Set variables for editor windows.
2062 self.autosave.set(idleConf.GetOption(
2063 'main', 'General', 'autosave', default=0, type='bool'))
2064 self.format_width.set(idleConf.GetOption(
2065 'extensions', 'FormatParagraph', 'max-width', type='int'))
2066 self.context_lines.set(idleConf.GetOption(
Cheryl Sabella29996a12018-06-01 19:23:00 -04002067 'extensions', 'CodeContext', 'maxlines', type='int'))
wohlganger58fc71c2017-09-10 16:19:47 -05002068
Tal Einat604e7b92018-09-25 15:10:14 +03002069 # Set variables for shell windows.
2070 self.auto_squeeze_min_lines.set(idleConf.GetOption(
2071 'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
2072
csabellae8eb17b2017-07-30 18:39:17 -04002073 # Set additional help sources.
2074 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
2075 self.helplist.delete(0, 'end')
2076 for help_item in self.user_helplist:
2077 self.helplist.insert(END, help_item[0])
2078 self.set_add_delete_state()
2079
2080 def help_source_selected(self, event):
2081 "Handle event for selecting additional help."
2082 self.set_add_delete_state()
2083
2084 def set_add_delete_state(self):
2085 "Toggle the state for the help list buttons based on list entries."
2086 if self.helplist.size() < 1: # No entries in list.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002087 self.button_helplist_edit.state(('disabled',))
2088 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002089 else: # Some entries.
2090 if self.helplist.curselection(): # There currently is a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002091 self.button_helplist_edit.state(('!disabled',))
2092 self.button_helplist_remove.state(('!disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002093 else: # There currently is not a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002094 self.button_helplist_edit.state(('disabled',))
2095 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002096
2097 def helplist_item_add(self):
2098 """Handle add button for the help list.
2099
2100 Query for name and location of new help sources and add
2101 them to the list.
2102 """
2103 help_source = HelpSource(self, 'New Help Source').result
2104 if help_source:
2105 self.user_helplist.append(help_source)
2106 self.helplist.insert(END, help_source[0])
2107 self.update_help_changes()
2108
2109 def helplist_item_edit(self):
2110 """Handle edit button for the help list.
2111
2112 Query with existing help source information and update
2113 config if the values are changed.
2114 """
2115 item_index = self.helplist.index(ANCHOR)
2116 help_source = self.user_helplist[item_index]
2117 new_help_source = HelpSource(
2118 self, 'Edit Help Source',
2119 menuitem=help_source[0],
2120 filepath=help_source[1],
2121 ).result
2122 if new_help_source and new_help_source != help_source:
2123 self.user_helplist[item_index] = new_help_source
2124 self.helplist.delete(item_index)
2125 self.helplist.insert(item_index, new_help_source[0])
2126 self.update_help_changes()
2127 self.set_add_delete_state() # Selected will be un-selected
2128
2129 def helplist_item_remove(self):
2130 """Handle remove button for the help list.
2131
2132 Delete the help list item from config.
2133 """
2134 item_index = self.helplist.index(ANCHOR)
2135 del(self.user_helplist[item_index])
2136 self.helplist.delete(item_index)
2137 self.update_help_changes()
2138 self.set_add_delete_state()
2139
2140 def update_help_changes(self):
2141 "Clear and rebuild the HelpFiles section in changes"
2142 changes['main']['HelpFiles'] = {}
2143 for num in range(1, len(self.user_helplist) + 1):
2144 changes.add_option(
2145 'main', 'HelpFiles', str(num),
2146 ';'.join(self.user_helplist[num-1][:2]))
2147
2148
csabella45bf7232017-07-26 19:09:58 -04002149class VarTrace:
2150 """Maintain Tk variables trace state."""
2151
2152 def __init__(self):
2153 """Store Tk variables and callbacks.
2154
2155 untraced: List of tuples (var, callback)
2156 that do not have the callback attached
2157 to the Tk var.
2158 traced: List of tuples (var, callback) where
2159 that callback has been attached to the var.
2160 """
2161 self.untraced = []
2162 self.traced = []
2163
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002164 def clear(self):
2165 "Clear lists (for tests)."
Terry Jan Reedy733d0f62017-08-07 14:22:44 -04002166 # Call after all tests in a module to avoid memory leaks.
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002167 self.untraced.clear()
2168 self.traced.clear()
2169
csabella45bf7232017-07-26 19:09:58 -04002170 def add(self, var, callback):
2171 """Add (var, callback) tuple to untraced list.
2172
2173 Args:
2174 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04002175 callback: Either function name to be used as a callback
2176 or a tuple with IdleConf config-type, section, and
2177 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04002178
2179 Return:
2180 Tk variable instance.
2181 """
2182 if isinstance(callback, tuple):
2183 callback = self.make_callback(var, callback)
2184 self.untraced.append((var, callback))
2185 return var
2186
2187 @staticmethod
2188 def make_callback(var, config):
2189 "Return default callback function to add values to changes instance."
2190 def default_callback(*params):
2191 "Add config values to changes instance."
2192 changes.add_option(*config, var.get())
2193 return default_callback
2194
2195 def attach(self):
2196 "Attach callback to all vars that are not traced."
2197 while self.untraced:
2198 var, callback = self.untraced.pop()
2199 var.trace_add('write', callback)
2200 self.traced.append((var, callback))
2201
2202 def detach(self):
2203 "Remove callback from traced vars."
2204 while self.traced:
2205 var, callback = self.traced.pop()
2206 var.trace_remove('write', var.trace_info()[0][1])
2207 self.untraced.append((var, callback))
2208
2209
csabella5b591542017-07-28 14:40:59 -04002210tracers = VarTrace()
2211
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002212help_common = '''\
2213When you click either the Apply or Ok buttons, settings in this
2214dialog that are different from IDLE's default are saved in
2215a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002216these changes apply to all versions of IDLE installed on this
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002217machine. [Cancel] only cancels changes made since the last save.
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002218'''
2219help_pages = {
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002220 'Fonts/Tabs':'''
2221Font sample: This shows what a selection of Basic Multilingual Plane
2222unicode characters look like for the current font selection. If the
2223selected font does not define a character, Tk attempts to find another
2224font that does. Substitute glyphs depend on what is available on a
2225particular system and will not necessarily have the same size as the
2226font selected. Line contains 20 characters up to Devanagari, 14 for
2227Tamil, and 10 for East Asia.
2228
2229Hebrew and Arabic letters should display right to left, starting with
2230alef, \u05d0 and \u0627. Arabic digits display left to right. The
2231Devanagari and Tamil lines start with digits. The East Asian lines
2232are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
2233Hiragana and Katakana.
Serhiy Storchakaed6554c2017-10-28 03:22:44 +03002234
2235You can edit the font sample. Changes remain until IDLE is closed.
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002236''',
Cheryl Sabella3866d9b2017-09-10 22:41:10 -04002237 'Highlights': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002238Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002239The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002240be used with older IDLE releases if it is saved as a custom
2241theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04002242''',
2243 'Keys': '''
2244Keys:
2245The IDLE Modern Unix key set is new in June 2016. It can only
2246be used with older IDLE releases if it is saved as a custom
2247key set, with a different name.
2248''',
wohlganger58fc71c2017-09-10 16:19:47 -05002249 'General': '''
2250General:
wohlgangerfae2c352017-06-27 21:36:23 -05002251
penguindustin96466302019-05-06 14:57:17 -04002252AutoComplete: Popupwait is milliseconds to wait after key char, without
wohlgangerfae2c352017-06-27 21:36:23 -05002253cursor movement, before popping up completion box. Key char is '.' after
2254identifier or a '/' (or '\\' on Windows) within a string.
2255
2256FormatParagraph: Max-width is max chars in lines after re-formatting.
2257Use with paragraphs in both strings and comment blocks.
2258
2259ParenMatch: Style indicates what is highlighted when closer is entered:
2260'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
2261'expression' (default) - also everything in between. Flash-delay is how
2262long to highlight if cursor is not moved (0 means forever).
Cheryl Sabella29996a12018-06-01 19:23:00 -04002263
2264CodeContext: Maxlines is the maximum number of code context lines to
2265display when Code Context is turned on for an editor window.
Tal Einat604e7b92018-09-25 15:10:14 +03002266
2267Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
2268of output to automatically "squeeze".
wohlgangerfae2c352017-06-27 21:36:23 -05002269'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002270}
2271
Steven M. Gavac11ccf32001-09-24 09:43:17 +00002272
Terry Jan Reedy93f35422015-10-13 22:03:51 -04002273def is_int(s):
2274 "Return 's is blank or represents an int'"
2275 if not s:
2276 return True
2277 try:
2278 int(s)
2279 return True
2280 except ValueError:
2281 return False
2282
2283
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002284class VerticalScrolledFrame(Frame):
2285 """A pure Tkinter vertically scrollable frame.
2286
2287 * Use the 'interior' attribute to place widgets inside the scrollable frame
2288 * Construct and pack/place/grid normally
2289 * This frame only allows vertical scrolling
2290 """
2291 def __init__(self, parent, *args, **kw):
2292 Frame.__init__(self, parent, *args, **kw)
2293
csabella7eb58832017-07-04 21:30:58 -04002294 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002295 vscrollbar = Scrollbar(self, orient=VERTICAL)
2296 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
Cheryl Sabella7028e592017-08-26 14:26:02 -04002297 canvas = Canvas(self, borderwidth=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04002298 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002299 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
2300 vscrollbar.config(command=canvas.yview)
2301
csabella7eb58832017-07-04 21:30:58 -04002302 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002303 canvas.xview_moveto(0)
2304 canvas.yview_moveto(0)
2305
csabella7eb58832017-07-04 21:30:58 -04002306 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002307 self.interior = interior = Frame(canvas)
2308 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
2309
csabella7eb58832017-07-04 21:30:58 -04002310 # Track changes to the canvas and frame width and sync them,
2311 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002312 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04002313 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002314 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
2315 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002316 interior.bind('<Configure>', _configure_interior)
2317
2318 def _configure_canvas(event):
2319 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04002320 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002321 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
2322 canvas.bind('<Configure>', _configure_canvas)
2323
2324 return
2325
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002326
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00002327if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -04002328 from unittest import main
2329 main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
2330
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04002331 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04002332 run(ConfigDialog)