blob: e3fa34f2090e42cc060fff5ca91fbee9440c2dd1 [file] [log] [blame]
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +00001"""IDLE Configuration Dialog: support user customization of IDLE by GUI
2
3Customize font faces, sizes, and colorization attributes. Set indentation
4defaults. Customize keybindings. Colorization and keybindings can be
5saved as user defined sets. Select startup options including shell/editor
6and default window size. Define additional help sources.
7
8Note that tab width in IDLE is currently fixed at eight due to Tk issues.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00009Refer to comments in EditorWindow autoindent code for details.
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +000010
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000011"""
Tal Einat1ebee372019-07-23 13:02:11 +030012import re
13
Victor Stinner6900f162020-04-30 03:28:51 +020014from tkinter import (Toplevel, Listbox, Scale, Canvas,
csabellabac7d332017-06-26 17:46:26 -040015 StringVar, BooleanVar, IntVar, TRUE, FALSE,
Terry Jan Reedye8f7c782017-11-28 21:52:32 -050016 TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE,
17 NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
Louie Lubb2bae82017-07-10 06:57:18 +080018 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
Terry Jan Reedyaff0ada2019-01-02 22:04:06 -050019from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label,
wohlganger58fc71c2017-09-10 16:19:47 -050020 OptionMenu, Notebook, Radiobutton, Scrollbar, Style)
Terry Jan Reedy879986d2021-01-25 06:33:18 -050021from tkinter import colorchooser
22import tkinter.font as tkfont
Terry Jan Reedy3457f422017-08-27 16:39:41 -040023from tkinter import messagebox
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000024
terryjreedy349abd92017-07-07 16:00:57 -040025from idlelib.config import idleConf, ConfigChanges
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040026from idlelib.config_key import GetKeysDialog
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040027from idlelib.dynoption import DynOptionMenu
28from idlelib import macosx
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -040029from idlelib.query import SectionName, HelpSource
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040030from idlelib.textview import view_text
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -040031from idlelib.autocomplete import AutoComplete
32from idlelib.codecontext import CodeContext
33from idlelib.parenmatch import ParenMatch
Cheryl Sabella82494aa2019-07-17 09:44:44 -040034from idlelib.format import FormatParagraph
Tal Einat604e7b92018-09-25 15:10:14 +030035from idlelib.squeezer import Squeezer
Tal Einat3221a632019-07-27 19:57:48 +030036from idlelib.textview import ScrollableTextFrame
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -040037
terryjreedy349abd92017-07-07 16:00:57 -040038changes = ConfigChanges()
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -040039# Reload changed options in the following classes.
Tal Einat604e7b92018-09-25 15:10:14 +030040reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
41 Squeezer)
terryjreedy349abd92017-07-07 16:00:57 -040042
csabella5b591542017-07-28 14:40:59 -040043
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000044class ConfigDialog(Toplevel):
csabella7eb58832017-07-04 21:30:58 -040045 """Config dialog for IDLE.
46 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000047
Terry Jan Reedybfebfd82017-09-30 17:37:53 -040048 def __init__(self, parent, title='', *, _htest=False, _utest=False):
csabella7eb58832017-07-04 21:30:58 -040049 """Show the tabbed dialog for user configuration.
50
csabella36329a42017-07-13 23:32:01 -040051 Args:
52 parent - parent of this dialog
53 title - string which is the title of this popup dialog
54 _htest - bool, change box location when running htest
55 _utest - bool, don't wait_window when running unittest
56
57 Note: Focus set on font page fontlist.
58
59 Methods:
60 create_widgets
61 cancel: Bound to DELETE_WINDOW protocol.
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040062 """
Steven M. Gavad721c482001-07-31 10:46:53 +000063 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040064 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040065 if _htest:
66 parent.instance_dict = {}
Louie Lu9b622fb2017-07-14 08:35:48 +080067 if not _utest:
68 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000069
Terry Jan Reedycd567362014-10-17 01:31:35 -040070 self.title(title or 'IDLE Preferences')
csabellabac7d332017-06-26 17:46:26 -040071 x = parent.winfo_rootx() + 20
72 y = parent.winfo_rooty() + (30 if not _htest else 150)
73 self.geometry(f'+{x}+{y}')
csabella7eb58832017-07-04 21:30:58 -040074 # Each theme element key is its display name.
75 # The first value of the tuple is the sample area tag name.
76 # The second value is the display name list sort index.
csabellabac7d332017-06-26 17:46:26 -040077 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040078 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000079 self.transient(parent)
csabellabac7d332017-06-26 17:46:26 -040080 self.protocol("WM_DELETE_WINDOW", self.cancel)
csabella9397e2a2017-07-30 13:34:25 -040081 self.fontpage.fontlist.focus_set()
csabella7eb58832017-07-04 21:30:58 -040082 # XXX Decide whether to keep or delete these key bindings.
83 # Key bindings for this dialog.
84 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
85 # self.bind('<Alt-a>', self.Apply) #apply changes, save
86 # self.bind('<F1>', self.Help) #context help
Cheryl Sabella8f7a7982017-08-19 22:04:40 -040087 # Attach callbacks after loading config to avoid calling them.
csabella5b591542017-07-28 14:40:59 -040088 tracers.attach()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000089
Terry Jan Reedycfa89502014-07-14 23:07:32 -040090 if not _utest:
Louie Lu9b622fb2017-07-14 08:35:48 +080091 self.grab_set()
Terry Jan Reedycfa89502014-07-14 23:07:32 -040092 self.wm_deiconify()
93 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000094
csabellabac7d332017-06-26 17:46:26 -040095 def create_widgets(self):
csabella36329a42017-07-13 23:32:01 -040096 """Create and place widgets for tabbed dialog.
97
98 Widgets Bound to self:
Mark Rosemanc579ad12020-10-24 16:45:00 -070099 frame: encloses all other widgets
csabellae8eb17b2017-07-30 18:39:17 -0400100 note: Notebook
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400101 highpage: HighPage
csabellae8eb17b2017-07-30 18:39:17 -0400102 fontpage: FontPage
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400103 keyspage: KeysPage
csabellae8eb17b2017-07-30 18:39:17 -0400104 genpage: GenPage
Cheryl Sabellae36d9f52017-08-15 18:26:23 -0400105 extpage: self.create_page_extensions
csabella36329a42017-07-13 23:32:01 -0400106
107 Methods:
csabella36329a42017-07-13 23:32:01 -0400108 create_action_buttons
109 load_configs: Load pages except for extensions.
csabella36329a42017-07-13 23:32:01 -0400110 activate_config_changes: Tell editors to reload.
111 """
Mark Rosemanc579ad12020-10-24 16:45:00 -0700112 self.frame = frame = Frame(self, padding="5px")
113 self.frame.grid(sticky="nwes")
114 self.note = note = Notebook(frame)
Miss Islington (bot)33a7a242021-06-08 19:11:26 -0700115 self.extpage = ExtPage(note)
116 self.highpage = HighPage(note, self.extpage)
csabella9397e2a2017-07-30 13:34:25 -0400117 self.fontpage = FontPage(note, self.highpage)
Miss Islington (bot)33a7a242021-06-08 19:11:26 -0700118 self.keyspage = KeysPage(note, self.extpage)
Miss Islington (bot)664ae292021-06-09 13:37:56 -0700119 self.winpage = WinPage(note)
120 self.shedpage = ShedPage(note)
121
csabella9397e2a2017-07-30 13:34:25 -0400122 note.add(self.fontpage, text='Fonts/Tabs')
123 note.add(self.highpage, text='Highlights')
124 note.add(self.keyspage, text=' Keys ')
Miss Islington (bot)664ae292021-06-09 13:37:56 -0700125 note.add(self.winpage, text=' Windows ')
126 note.add(self.shedpage, text=' Shell/Ed ')
csabella9397e2a2017-07-30 13:34:25 -0400127 note.add(self.extpage, text='Extensions')
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400128 note.enable_traversal()
129 note.pack(side=TOP, expand=TRUE, fill=BOTH)
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400130 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400131
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400132 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400133 """Return frame of action buttons for dialog.
134
135 Methods:
136 ok
137 apply
138 cancel
139 help
140
141 Widget Structure:
142 outer: Frame
143 buttons: Frame
144 (no assignment): Button (ok)
145 (no assignment): Button (apply)
146 (no assignment): Button (cancel)
147 (no assignment): Button (help)
148 (no assignment): Frame
149 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400150 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400151 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400152 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400153 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000154 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400155 padding_args = {'padding': (6, 3)}
Mark Rosemanc579ad12020-10-24 16:45:00 -0700156 outer = Frame(self.frame, padding=2)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500157 buttons_frame = Frame(outer, padding=2)
158 self.buttons = {}
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400159 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400160 ('Ok', self.ok),
161 ('Apply', self.apply),
162 ('Cancel', self.cancel),
163 ('Help', self.help)):
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500164 self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
165 takefocus=FALSE, **padding_args)
166 self.buttons[txt].pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400167 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400168 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500169 buttons_frame.pack(side=BOTTOM)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400170 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400171
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400172 def ok(self):
173 """Apply config changes, then dismiss dialog.
174
175 Methods:
176 apply
177 destroy: inherited
178 """
179 self.apply()
180 self.destroy()
181
182 def apply(self):
183 """Apply config changes and leave dialog open.
184
185 Methods:
186 deactivate_current_config
187 save_all_changed_extensions
188 activate_config_changes
189 """
190 self.deactivate_current_config()
191 changes.save_all()
192 self.save_all_changed_extensions()
193 self.activate_config_changes()
194
195 def cancel(self):
196 """Dismiss config dialog.
197
198 Methods:
199 destroy: inherited
200 """
Cheryl Sabellad0d9fa82020-01-25 04:00:54 -0500201 changes.clear()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400202 self.destroy()
203
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300204 def destroy(self):
205 global font_sample_text
206 font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
Tal Einat10ea9402018-08-02 09:18:29 +0300207 self.grab_release()
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300208 super().destroy()
209
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400210 def help(self):
211 """Create textview for config dialog help.
212
Xtreakd9677f32019-06-03 09:51:15 +0530213 Attributes accessed:
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400214 note
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400215 Methods:
216 view_text: Method from textview module.
217 """
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400218 page = self.note.tab(self.note.select(), option='text').strip()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400219 view_text(self, title='Help for IDLE preferences',
Zackery Spytz2e43b642020-01-22 20:54:30 -0700220 contents=help_common+help_pages.get(page, ''))
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400221
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400222 def deactivate_current_config(self):
223 """Remove current key bindings.
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400224 Iterate over window instances defined in parent and remove
225 the keybindings.
226 """
227 # Before a config is saved, some cleanup of current
228 # config must be done - remove the previous keybindings.
229 win_instances = self.parent.instance_dict.keys()
230 for instance in win_instances:
231 instance.RemoveKeybindings()
232
233 def activate_config_changes(self):
234 """Apply configuration changes to current windows.
235
236 Dynamically update the current parent window instances
237 with some of the configuration changes.
238 """
239 win_instances = self.parent.instance_dict.keys()
240 for instance in win_instances:
241 instance.ResetColorizer()
242 instance.ResetFont()
243 instance.set_notabs_indentwidth()
244 instance.ApplyKeybindings()
245 instance.reset_help_menu_entries()
Zackery Spytz9c284492019-11-13 00:13:33 -0700246 instance.update_cursor_blink()
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -0400247 for klass in reloadables:
248 klass.reload()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400249
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400250
csabella6f446be2017-08-01 00:24:07 -0400251# class TabPage(Frame): # A template for Page classes.
252# def __init__(self, master):
253# super().__init__(master)
254# self.create_page_tab()
255# self.load_tab_cfg()
256# def create_page_tab(self):
257# # Define tk vars and register var and callback with tracers.
258# # Create subframes and widgets.
259# # Pack widgets.
260# def load_tab_cfg(self):
261# # Initialize widgets with data from idleConf.
262# def var_changed_var_name():
263# # For each tk var that needs other than default callback.
264# def other_methods():
265# # Define tab-specific behavior.
266
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300267font_sample_text = (
268 '<ASCII/Latin1>\n'
269 'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
270 '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
271 '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
272 '\n<IPA,Greek,Cyrillic>\n'
273 '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
274 '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
275 '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
276 '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
277 '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
278 '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
279 '\n<Hebrew, Arabic>\n'
280 '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
281 '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
282 '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
283 '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
284 '\n<Devanagari, Tamil>\n'
285 '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
286 '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
287 '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
288 '\u0b85\u0b87\u0b89\u0b8e\n'
289 '\n<East Asian>\n'
290 '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
291 '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
292 '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
293 '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
294 )
295
csabella6f446be2017-08-01 00:24:07 -0400296
csabella9397e2a2017-07-30 13:34:25 -0400297class FontPage(Frame):
298
csabella6f446be2017-08-01 00:24:07 -0400299 def __init__(self, master, highpage):
300 super().__init__(master)
csabella9397e2a2017-07-30 13:34:25 -0400301 self.highlight_sample = highpage.highlight_sample
302 self.create_page_font_tab()
303 self.load_font_cfg()
304 self.load_tab_cfg()
305
306 def create_page_font_tab(self):
307 """Return frame of widgets for Font/Tabs tab.
308
309 Fonts: Enable users to provisionally change font face, size, or
310 boldness and to see the consequence of proposed choices. Each
311 action set 3 options in changes structuree and changes the
312 corresponding aspect of the font sample on this page and
313 highlight sample on highlight page.
314
315 Function load_font_cfg initializes font vars and widgets from
316 idleConf entries and tk.
317
318 Fontlist: mouse button 1 click or up or down key invoke
319 on_fontlist_select(), which sets var font_name.
320
321 Sizelist: clicking the menubutton opens the dropdown menu. A
322 mouse button 1 click or return key sets var font_size.
323
324 Bold_toggle: clicking the box toggles var font_bold.
325
326 Changing any of the font vars invokes var_changed_font, which
327 adds all 3 font options to changes and calls set_samples.
328 Set_samples applies a new font constructed from the font vars to
Leo Ariasc3d95082018-02-03 18:36:10 -0600329 font_sample and to highlight_sample on the highlight page.
csabella9397e2a2017-07-30 13:34:25 -0400330
331 Tabs: Enable users to change spaces entered for indent tabs.
332 Changing indent_scale value with the mouse sets Var space_num,
333 which invokes the default callback to add an entry to
334 changes. Load_tab_cfg initializes space_num to default.
335
Cheryl Sabella2f896462017-08-14 21:21:43 -0400336 Widgets for FontPage(Frame): (*) widgets bound to self
337 frame_font: LabelFrame
338 frame_font_name: Frame
339 font_name_title: Label
340 (*)fontlist: ListBox - font_name
341 scroll_font: Scrollbar
342 frame_font_param: Frame
343 font_size_title: Label
344 (*)sizelist: DynOptionMenu - font_size
345 (*)bold_toggle: Checkbutton - font_bold
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400346 frame_sample: LabelFrame
347 (*)font_sample: Label
Cheryl Sabella2f896462017-08-14 21:21:43 -0400348 frame_indent: LabelFrame
349 indent_title: Label
350 (*)indent_scale: Scale - space_num
csabella9397e2a2017-07-30 13:34:25 -0400351 """
csabella6f446be2017-08-01 00:24:07 -0400352 self.font_name = tracers.add(StringVar(self), self.var_changed_font)
353 self.font_size = tracers.add(StringVar(self), self.var_changed_font)
354 self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
csabella9397e2a2017-07-30 13:34:25 -0400355 self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
356
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400357 # Define frames and widgets.
csabella9397e2a2017-07-30 13:34:25 -0400358 frame_font = LabelFrame(
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400359 self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ')
360 frame_sample = LabelFrame(
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300361 self, borderwidth=2, relief=GROOVE,
362 text=' Font Sample (Editable) ')
csabella9397e2a2017-07-30 13:34:25 -0400363 frame_indent = LabelFrame(
csabella6f446be2017-08-01 00:24:07 -0400364 self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
csabella9397e2a2017-07-30 13:34:25 -0400365 # frame_font.
366 frame_font_name = Frame(frame_font)
367 frame_font_param = Frame(frame_font)
368 font_name_title = Label(
369 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400370 self.fontlist = Listbox(frame_font_name, height=15,
csabella9397e2a2017-07-30 13:34:25 -0400371 takefocus=True, exportselection=FALSE)
372 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
373 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
374 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
375 scroll_font = Scrollbar(frame_font_name)
376 scroll_font.config(command=self.fontlist.yview)
377 self.fontlist.config(yscrollcommand=scroll_font.set)
378 font_size_title = Label(frame_font_param, text='Size :')
379 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
380 self.bold_toggle = Checkbutton(
381 frame_font_param, variable=self.font_bold,
382 onvalue=1, offvalue=0, text='Bold')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400383 # frame_sample.
Tal Einat3221a632019-07-27 19:57:48 +0300384 font_sample_frame = ScrollableTextFrame(frame_sample)
385 self.font_sample = font_sample_frame.text
386 self.font_sample.config(wrap=NONE, width=1, height=1)
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300387 self.font_sample.insert(END, font_sample_text)
csabella9397e2a2017-07-30 13:34:25 -0400388 # frame_indent.
389 indent_title = Label(
390 frame_indent, justify=LEFT,
391 text='Python Standard: 4 Spaces!')
392 self.indent_scale = Scale(
393 frame_indent, variable=self.space_num,
394 orient='horizontal', tickinterval=2, from_=2, to=16)
395
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400396 # Grid and pack widgets:
397 self.columnconfigure(1, weight=1)
Tal Einat3221a632019-07-27 19:57:48 +0300398 self.rowconfigure(2, weight=1)
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400399 frame_font.grid(row=0, column=0, padx=5, pady=5)
Tal Einat3221a632019-07-27 19:57:48 +0300400 frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400401 sticky='nsew')
402 frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
csabella9397e2a2017-07-30 13:34:25 -0400403 # frame_font.
404 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
405 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
406 font_name_title.pack(side=TOP, anchor=W)
407 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
408 scroll_font.pack(side=LEFT, fill=Y)
409 font_size_title.pack(side=LEFT, anchor=W)
410 self.sizelist.pack(side=LEFT, anchor=W)
411 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400412 # frame_sample.
Tal Einat3221a632019-07-27 19:57:48 +0300413 font_sample_frame.pack(expand=TRUE, fill=BOTH)
csabella9397e2a2017-07-30 13:34:25 -0400414 # frame_indent.
csabella9397e2a2017-07-30 13:34:25 -0400415 indent_title.pack(side=TOP, anchor=W, padx=5)
416 self.indent_scale.pack(side=TOP, padx=5, fill=X)
417
csabella9397e2a2017-07-30 13:34:25 -0400418 def load_font_cfg(self):
419 """Load current configuration settings for the font options.
420
421 Retrieve current font with idleConf.GetFont and font families
422 from tk. Setup fontlist and set font_name. Setup sizelist,
423 which sets font_size. Set font_bold. Call set_samples.
424 """
425 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
426 font_name = configured_font[0].lower()
427 font_size = configured_font[1]
428 font_bold = configured_font[2]=='bold'
429
Terry Jan Reedy96ce2272020-02-10 20:08:58 -0500430 # Set sorted no-duplicate editor font selection list and font_name.
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500431 fonts = sorted(set(tkfont.families(self)))
csabella9397e2a2017-07-30 13:34:25 -0400432 for font in fonts:
433 self.fontlist.insert(END, font)
434 self.font_name.set(font_name)
435 lc_fonts = [s.lower() for s in fonts]
436 try:
437 current_font_index = lc_fonts.index(font_name)
438 self.fontlist.see(current_font_index)
439 self.fontlist.select_set(current_font_index)
440 self.fontlist.select_anchor(current_font_index)
441 self.fontlist.activate(current_font_index)
442 except ValueError:
443 pass
444 # Set font size dropdown.
445 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
446 '16', '18', '20', '22', '25', '29', '34', '40'),
447 font_size)
448 # Set font weight.
449 self.font_bold.set(font_bold)
450 self.set_samples()
451
452 def var_changed_font(self, *params):
453 """Store changes to font attributes.
454
455 When one font attribute changes, save them all, as they are
456 not independent from each other. In particular, when we are
457 overriding the default font, we need to write out everything.
458 """
459 value = self.font_name.get()
460 changes.add_option('main', 'EditorWindow', 'font', value)
461 value = self.font_size.get()
462 changes.add_option('main', 'EditorWindow', 'font-size', value)
463 value = self.font_bold.get()
464 changes.add_option('main', 'EditorWindow', 'font-bold', value)
465 self.set_samples()
466
467 def on_fontlist_select(self, event):
468 """Handle selecting a font from the list.
469
470 Event can result from either mouse click or Up or Down key.
471 Set font_name and example displays to selection.
472 """
473 font = self.fontlist.get(
474 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
475 self.font_name.set(font.lower())
476
477 def set_samples(self, event=None):
478 """Update update both screen samples with the font settings.
479
480 Called on font initialization and change events.
481 Accesses font_name, font_size, and font_bold Variables.
Leo Ariasc3d95082018-02-03 18:36:10 -0600482 Updates font_sample and highlight page highlight_sample.
csabella9397e2a2017-07-30 13:34:25 -0400483 """
484 font_name = self.font_name.get()
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500485 font_weight = tkfont.BOLD if self.font_bold.get() else tkfont.NORMAL
csabella9397e2a2017-07-30 13:34:25 -0400486 new_font = (font_name, self.font_size.get(), font_weight)
487 self.font_sample['font'] = new_font
488 self.highlight_sample['font'] = new_font
489
490 def load_tab_cfg(self):
491 """Load current configuration settings for the tab options.
492
493 Attributes updated:
494 space_num: Set to value from idleConf.
495 """
496 # Set indent sizes.
497 space_num = idleConf.GetOption(
498 'main', 'Indent', 'num-spaces', default=4, type='int')
499 self.space_num.set(space_num)
500
501 def var_changed_space_num(self, *params):
502 "Store change to indentation size."
503 value = self.space_num.get()
504 changes.add_option('main', 'Indent', 'num-spaces', value)
505
506
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400507class HighPage(Frame):
508
Miss Islington (bot)33a7a242021-06-08 19:11:26 -0700509 def __init__(self, master, extpage):
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400510 super().__init__(master)
Miss Islington (bot)33a7a242021-06-08 19:11:26 -0700511 self.extpage = extpage
Mark Rosemanc579ad12020-10-24 16:45:00 -0700512 self.cd = master.winfo_toplevel()
Cheryl Sabella7028e592017-08-26 14:26:02 -0400513 self.style = Style(master)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400514 self.create_page_highlight()
515 self.load_theme_cfg()
516
517 def create_page_highlight(self):
518 """Return frame of widgets for Highlighting tab.
519
520 Enable users to provisionally change foreground and background
521 colors applied to textual tags. Color mappings are stored in
522 complete listings called themes. Built-in themes in
523 idlelib/config-highlight.def are fixed as far as the dialog is
524 concerned. Any theme can be used as the base for a new custom
525 theme, stored in .idlerc/config-highlight.cfg.
526
527 Function load_theme_cfg() initializes tk variables and theme
528 lists and calls paint_theme_sample() and set_highlight_target()
529 for the current theme. Radiobuttons builtin_theme_on and
530 custom_theme_on toggle var theme_source, which controls if the
531 current set of colors are from a builtin or custom theme.
532 DynOptionMenus builtinlist and customlist contain lists of the
533 builtin and custom themes, respectively, and the current item
534 from each list is stored in vars builtin_name and custom_name.
535
536 Function paint_theme_sample() applies the colors from the theme
537 to the tags in text widget highlight_sample and then invokes
538 set_color_sample(). Function set_highlight_target() sets the state
539 of the radiobuttons fg_on and bg_on based on the tag and it also
540 invokes set_color_sample().
541
542 Function set_color_sample() sets the background color for the frame
543 holding the color selector. This provides a larger visual of the
544 color for the current tag and plane (foreground/background).
545
546 Note: set_color_sample() is called from many places and is often
547 called more than once when a change is made. It is invoked when
548 foreground or background is selected (radiobuttons), from
549 paint_theme_sample() (theme is changed or load_cfg is called), and
550 from set_highlight_target() (target tag is changed or load_cfg called).
551
552 Button delete_custom invokes delete_custom() to delete
553 a custom theme from idleConf.userCfg['highlight'] and changes.
554 Button save_custom invokes save_as_new_theme() which calls
555 get_new_theme_name() and create_new() to save a custom theme
556 and its colors to idleConf.userCfg['highlight'].
557
558 Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
559 if the current selected color for a tag is for the foreground or
560 background.
561
562 DynOptionMenu targetlist contains a readable description of the
563 tags applied to Python source within IDLE. Selecting one of the
564 tags from this list populates highlight_target, which has a callback
565 function set_highlight_target().
566
567 Text widget highlight_sample displays a block of text (which is
568 mock Python code) in which is embedded the defined tags and reflects
569 the color attributes of the current theme and changes for those tags.
570 Mouse button 1 allows for selection of a tag and updates
571 highlight_target with that tag value.
572
573 Note: The font in highlight_sample is set through the config in
574 the fonts tab.
575
576 In other words, a tag can be selected either from targetlist or
577 by clicking on the sample text within highlight_sample. The
578 plane (foreground/background) is selected via the radiobutton.
579 Together, these two (tag and plane) control what color is
580 shown in set_color_sample() for the current theme. Button set_color
581 invokes get_color() which displays a ColorChooser to change the
582 color for the selected tag/plane. If a new color is picked,
583 it will be saved to changes and the highlight_sample and
584 frame background will be updated.
585
586 Tk Variables:
587 color: Color of selected target.
588 builtin_name: Menu variable for built-in theme.
589 custom_name: Menu variable for custom theme.
590 fg_bg_toggle: Toggle for foreground/background color.
591 Note: this has no callback.
592 theme_source: Selector for built-in or custom theme.
593 highlight_target: Menu variable for the highlight tag target.
594
595 Instance Data Attributes:
596 theme_elements: Dictionary of tags for text highlighting.
597 The key is the display name and the value is a tuple of
598 (tag name, display sort order).
599
600 Methods [attachment]:
601 load_theme_cfg: Load current highlight colors.
602 get_color: Invoke colorchooser [button_set_color].
603 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
604 set_highlight_target: set fg_bg_toggle, set_color_sample().
605 set_color_sample: Set frame background to target.
606 on_new_color_set: Set new color and add option.
607 paint_theme_sample: Recolor sample.
608 get_new_theme_name: Get from popup.
609 create_new: Combine theme with changes and save.
610 save_as_new_theme: Save [button_save_custom].
611 set_theme_type: Command for [theme_source].
612 delete_custom: Activate default [button_delete_custom].
613 save_new: Save to userCfg['theme'] (is function).
614
615 Widgets of highlights page frame: (*) widgets bound to self
616 frame_custom: LabelFrame
617 (*)highlight_sample: Text
618 (*)frame_color_set: Frame
619 (*)button_set_color: Button
620 (*)targetlist: DynOptionMenu - highlight_target
621 frame_fg_bg_toggle: Frame
622 (*)fg_on: Radiobutton - fg_bg_toggle
623 (*)bg_on: Radiobutton - fg_bg_toggle
624 (*)button_save_custom: Button
625 frame_theme: LabelFrame
626 theme_type_title: Label
627 (*)builtin_theme_on: Radiobutton - theme_source
628 (*)custom_theme_on: Radiobutton - theme_source
629 (*)builtinlist: DynOptionMenu - builtin_name
630 (*)customlist: DynOptionMenu - custom_name
631 (*)button_delete_custom: Button
632 (*)theme_message: Label
633 """
634 self.theme_elements = {
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400635 'Normal Code or Text': ('normal', '00'),
Cheryl Sabellade651622018-06-01 21:45:54 -0400636 'Code Context': ('context', '01'),
637 'Python Keywords': ('keyword', '02'),
638 'Python Definitions': ('definition', '03'),
639 'Python Builtins': ('builtin', '04'),
640 'Python Comments': ('comment', '05'),
641 'Python Strings': ('string', '06'),
642 'Selected Text': ('hilite', '07'),
643 'Found Text': ('hit', '08'),
644 'Cursor': ('cursor', '09'),
645 'Editor Breakpoint': ('break', '10'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400646 'Shell Prompt': ('console', '11'),
647 'Error Text': ('error', '12'),
648 'Shell User Output': ('stdout', '13'),
649 'Shell User Exception': ('stderr', '14'),
Tal Einat7123ea02019-07-23 15:22:11 +0300650 'Line Number': ('linenumber', '16'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400651 }
652 self.builtin_name = tracers.add(
653 StringVar(self), self.var_changed_builtin_name)
654 self.custom_name = tracers.add(
655 StringVar(self), self.var_changed_custom_name)
656 self.fg_bg_toggle = BooleanVar(self)
657 self.color = tracers.add(
658 StringVar(self), self.var_changed_color)
659 self.theme_source = tracers.add(
660 BooleanVar(self), self.var_changed_theme_source)
661 self.highlight_target = tracers.add(
662 StringVar(self), self.var_changed_highlight_target)
663
664 # Create widgets:
665 # body frame and section frames.
666 frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
667 text=' Custom Highlighting ')
668 frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
669 text=' Highlighting Theme ')
670 # frame_custom.
Tal Einat3221a632019-07-27 19:57:48 +0300671 sample_frame = ScrollableTextFrame(
672 frame_custom, relief=SOLID, borderwidth=1)
673 text = self.highlight_sample = sample_frame.text
674 text.configure(
675 font=('courier', 12, ''), cursor='hand2', width=1, height=1,
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400676 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500677 # Prevent perhaps invisible selection of word or slice.
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400678 text.bind('<Double-Button-1>', lambda e: 'break')
679 text.bind('<B1-Motion>', lambda e: 'break')
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400680 string_tags=(
681 ('# Click selects item.', 'comment'), ('\n', 'normal'),
682 ('code context section', 'context'), ('\n', 'normal'),
683 ('| cursor', 'cursor'), ('\n', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400684 ('def', 'keyword'), (' ', 'normal'),
685 ('func', 'definition'), ('(param):\n ', 'normal'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400686 ('"Return None."', 'string'), ('\n var0 = ', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400687 ("'string'", 'string'), ('\n var1 = ', 'normal'),
688 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
689 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
690 ('list', 'builtin'), ('(', 'normal'),
691 ('None', 'keyword'), (')\n', 'normal'),
692 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400693 ('>>>', 'console'), (' 3.14**2\n', 'normal'),
694 ('9.8596', 'stdout'), ('\n', 'normal'),
695 ('>>>', 'console'), (' pri ', 'normal'),
696 ('n', 'error'), ('t(\n', 'normal'),
697 ('SyntaxError', 'stderr'), ('\n', 'normal'))
698 for string, tag in string_tags:
699 text.insert(END, string, tag)
Tal Einat7123ea02019-07-23 15:22:11 +0300700 n_lines = len(text.get('1.0', END).splitlines())
Tal Einat3221a632019-07-27 19:57:48 +0300701 for lineno in range(1, n_lines):
Tal Einat7123ea02019-07-23 15:22:11 +0300702 text.insert(f'{lineno}.0',
703 f'{lineno:{len(str(n_lines))}d} ',
704 'linenumber')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400705 for element in self.theme_elements:
706 def tem(event, elem=element):
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400707 # event.widget.winfo_top_level().highlight_target.set(elem)
708 self.highlight_target.set(elem)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400709 text.tag_bind(
710 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400711 text['state'] = 'disabled'
712 self.style.configure('frame_color_set.TFrame', borderwidth=1,
713 relief='solid')
714 self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400715 frame_fg_bg_toggle = Frame(frame_custom)
716 self.button_set_color = Button(
717 self.frame_color_set, text='Choose Color for :',
Cheryl Sabella7028e592017-08-26 14:26:02 -0400718 command=self.get_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400719 self.targetlist = DynOptionMenu(
720 self.frame_color_set, self.highlight_target, None,
721 highlightthickness=0) #, command=self.set_highlight_targetBinding
722 self.fg_on = Radiobutton(
723 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
724 text='Foreground', command=self.set_color_sample_binding)
725 self.bg_on = Radiobutton(
726 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
727 text='Background', command=self.set_color_sample_binding)
728 self.fg_bg_toggle.set(1)
729 self.button_save_custom = Button(
730 frame_custom, text='Save as New Custom Theme',
731 command=self.save_as_new_theme)
732 # frame_theme.
733 theme_type_title = Label(frame_theme, text='Select : ')
734 self.builtin_theme_on = Radiobutton(
735 frame_theme, variable=self.theme_source, value=1,
736 command=self.set_theme_type, text='a Built-in Theme')
737 self.custom_theme_on = Radiobutton(
738 frame_theme, variable=self.theme_source, value=0,
739 command=self.set_theme_type, text='a Custom Theme')
740 self.builtinlist = DynOptionMenu(
741 frame_theme, self.builtin_name, None, command=None)
742 self.customlist = DynOptionMenu(
743 frame_theme, self.custom_name, None, command=None)
744 self.button_delete_custom = Button(
745 frame_theme, text='Delete Custom Theme',
746 command=self.delete_custom)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400747 self.theme_message = Label(frame_theme, borderwidth=2)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400748 # Pack widgets:
749 # body.
750 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
wohlganger58fc71c2017-09-10 16:19:47 -0500751 frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400752 # frame_custom.
Tal Einat3221a632019-07-27 19:57:48 +0300753 self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400754 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Tal Einat3221a632019-07-27 19:57:48 +0300755 sample_frame.pack(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400756 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
757 self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
758 self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
759 self.fg_on.pack(side=LEFT, anchor=E)
760 self.bg_on.pack(side=RIGHT, anchor=W)
761 self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
762 # frame_theme.
763 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
764 self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
765 self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
766 self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
767 self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
768 self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
769 self.theme_message.pack(side=TOP, fill=X, pady=5)
770
771 def load_theme_cfg(self):
772 """Load current configuration settings for the theme options.
773
774 Based on the theme_source toggle, the theme is set as
775 either builtin or custom and the initial widget values
776 reflect the current settings from idleConf.
777
778 Attributes updated:
779 theme_source: Set from idleConf.
780 builtinlist: List of default themes from idleConf.
781 customlist: List of custom themes from idleConf.
782 custom_theme_on: Disabled if there are no custom themes.
783 custom_theme: Message with additional information.
784 targetlist: Create menu from self.theme_elements.
785
786 Methods:
787 set_theme_type
788 paint_theme_sample
789 set_highlight_target
790 """
791 # Set current theme type radiobutton.
792 self.theme_source.set(idleConf.GetOption(
793 'main', 'Theme', 'default', type='bool', default=1))
794 # Set current theme.
795 current_option = idleConf.CurrentTheme()
796 # Load available theme option menus.
797 if self.theme_source.get(): # Default theme selected.
798 item_list = idleConf.GetSectionList('default', 'highlight')
799 item_list.sort()
800 self.builtinlist.SetMenu(item_list, current_option)
801 item_list = idleConf.GetSectionList('user', 'highlight')
802 item_list.sort()
803 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400804 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400805 self.custom_name.set('- no custom themes -')
806 else:
807 self.customlist.SetMenu(item_list, item_list[0])
808 else: # User theme selected.
809 item_list = idleConf.GetSectionList('user', 'highlight')
810 item_list.sort()
811 self.customlist.SetMenu(item_list, current_option)
812 item_list = idleConf.GetSectionList('default', 'highlight')
813 item_list.sort()
814 self.builtinlist.SetMenu(item_list, item_list[0])
815 self.set_theme_type()
816 # Load theme element option menu.
817 theme_names = list(self.theme_elements.keys())
818 theme_names.sort(key=lambda x: self.theme_elements[x][1])
819 self.targetlist.SetMenu(theme_names, theme_names[0])
820 self.paint_theme_sample()
821 self.set_highlight_target()
822
823 def var_changed_builtin_name(self, *params):
824 """Process new builtin theme selection.
825
826 Add the changed theme's name to the changed_items and recreate
827 the sample with the values from the selected theme.
828 """
829 old_themes = ('IDLE Classic', 'IDLE New')
830 value = self.builtin_name.get()
831 if value not in old_themes:
832 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
833 changes.add_option('main', 'Theme', 'name', old_themes[0])
834 changes.add_option('main', 'Theme', 'name2', value)
835 self.theme_message['text'] = 'New theme, see Help'
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400836 else:
837 changes.add_option('main', 'Theme', 'name', value)
838 changes.add_option('main', 'Theme', 'name2', '')
839 self.theme_message['text'] = ''
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400840 self.paint_theme_sample()
841
842 def var_changed_custom_name(self, *params):
843 """Process new custom theme selection.
844
845 If a new custom theme is selected, add the name to the
846 changed_items and apply the theme to the sample.
847 """
848 value = self.custom_name.get()
849 if value != '- no custom themes -':
850 changes.add_option('main', 'Theme', 'name', value)
851 self.paint_theme_sample()
852
853 def var_changed_theme_source(self, *params):
854 """Process toggle between builtin and custom theme.
855
856 Update the default toggle value and apply the newly
857 selected theme type.
858 """
859 value = self.theme_source.get()
860 changes.add_option('main', 'Theme', 'default', value)
861 if value:
862 self.var_changed_builtin_name()
863 else:
864 self.var_changed_custom_name()
865
866 def var_changed_color(self, *params):
867 "Process change to color choice."
868 self.on_new_color_set()
869
870 def var_changed_highlight_target(self, *params):
871 "Process selection of new target tag for highlighting."
872 self.set_highlight_target()
873
874 def set_theme_type(self):
875 """Set available screen options based on builtin or custom theme.
876
877 Attributes accessed:
878 theme_source
879
880 Attributes updated:
881 builtinlist
882 customlist
883 button_delete_custom
884 custom_theme_on
885
886 Called from:
887 handler for builtin_theme_on and custom_theme_on
888 delete_custom
889 create_new
890 load_theme_cfg
891 """
892 if self.theme_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -0400893 self.builtinlist['state'] = 'normal'
894 self.customlist['state'] = 'disabled'
895 self.button_delete_custom.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400896 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400897 self.builtinlist['state'] = 'disabled'
898 self.custom_theme_on.state(('!disabled',))
899 self.customlist['state'] = 'normal'
900 self.button_delete_custom.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400901
902 def get_color(self):
903 """Handle button to select a new color for the target tag.
904
905 If a new color is selected while using a builtin theme, a
906 name must be supplied to create a custom theme.
907
908 Attributes accessed:
909 highlight_target
910 frame_color_set
911 theme_source
912
913 Attributes updated:
914 color
915
916 Methods:
917 get_new_theme_name
918 create_new
919 """
920 target = self.highlight_target.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -0400921 prev_color = self.style.lookup(self.frame_color_set['style'],
922 'background')
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500923 rgbTuplet, color_string = colorchooser.askcolor(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400924 parent=self, title='Pick new color for : '+target,
925 initialcolor=prev_color)
926 if color_string and (color_string != prev_color):
927 # User didn't cancel and they chose a new color.
928 if self.theme_source.get(): # Current theme is a built-in.
929 message = ('Your changes will be saved as a new Custom Theme. '
930 'Enter a name for your new Custom Theme below.')
931 new_theme = self.get_new_theme_name(message)
932 if not new_theme: # User cancelled custom theme creation.
933 return
934 else: # Create new custom theme based on previously active theme.
935 self.create_new(new_theme)
936 self.color.set(color_string)
937 else: # Current theme is user defined.
938 self.color.set(color_string)
939
940 def on_new_color_set(self):
941 "Display sample of new color selection on the dialog."
942 new_color = self.color.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -0400943 self.style.configure('frame_color_set.TFrame', background=new_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400944 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
945 sample_element = self.theme_elements[self.highlight_target.get()][0]
946 self.highlight_sample.tag_config(sample_element, **{plane: new_color})
947 theme = self.custom_name.get()
948 theme_element = sample_element + '-' + plane
949 changes.add_option('highlight', theme, theme_element, new_color)
950
951 def get_new_theme_name(self, message):
952 "Return name of new theme from query popup."
953 used_names = (idleConf.GetSectionList('user', 'highlight') +
954 idleConf.GetSectionList('default', 'highlight'))
955 new_theme = SectionName(
956 self, 'New Custom Theme', message, used_names).result
957 return new_theme
958
959 def save_as_new_theme(self):
960 """Prompt for new theme name and create the theme.
961
962 Methods:
963 get_new_theme_name
964 create_new
965 """
966 new_theme_name = self.get_new_theme_name('New Theme Name:')
967 if new_theme_name:
968 self.create_new(new_theme_name)
969
970 def create_new(self, new_theme_name):
971 """Create a new custom theme with the given name.
972
973 Create the new theme based on the previously active theme
974 with the current changes applied. Once it is saved, then
975 activate the new theme.
976
977 Attributes accessed:
978 builtin_name
979 custom_name
980
981 Attributes updated:
982 customlist
983 theme_source
984
985 Method:
986 save_new
987 set_theme_type
988 """
989 if self.theme_source.get():
990 theme_type = 'default'
991 theme_name = self.builtin_name.get()
992 else:
993 theme_type = 'user'
994 theme_name = self.custom_name.get()
995 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
996 # Apply any of the old theme's unsaved changes to the new theme.
997 if theme_name in changes['highlight']:
998 theme_changes = changes['highlight'][theme_name]
999 for element in theme_changes:
1000 new_theme[element] = theme_changes[element]
1001 # Save the new theme.
1002 self.save_new(new_theme_name, new_theme)
1003 # Change GUI over to the new theme.
1004 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
1005 custom_theme_list.sort()
1006 self.customlist.SetMenu(custom_theme_list, new_theme_name)
1007 self.theme_source.set(0)
1008 self.set_theme_type()
1009
1010 def set_highlight_target(self):
1011 """Set fg/bg toggle and color based on highlight tag target.
1012
1013 Instance variables accessed:
1014 highlight_target
1015
1016 Attributes updated:
1017 fg_on
1018 bg_on
1019 fg_bg_toggle
1020
1021 Methods:
1022 set_color_sample
1023
1024 Called from:
1025 var_changed_highlight_target
1026 load_theme_cfg
1027 """
1028 if self.highlight_target.get() == 'Cursor': # bg not possible
Cheryl Sabella7028e592017-08-26 14:26:02 -04001029 self.fg_on.state(('disabled',))
1030 self.bg_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001031 self.fg_bg_toggle.set(1)
1032 else: # Both fg and bg can be set.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001033 self.fg_on.state(('!disabled',))
1034 self.bg_on.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001035 self.fg_bg_toggle.set(1)
1036 self.set_color_sample()
1037
1038 def set_color_sample_binding(self, *args):
1039 """Change color sample based on foreground/background toggle.
1040
1041 Methods:
1042 set_color_sample
1043 """
1044 self.set_color_sample()
1045
1046 def set_color_sample(self):
1047 """Set the color of the frame background to reflect the selected target.
1048
1049 Instance variables accessed:
1050 theme_elements
1051 highlight_target
1052 fg_bg_toggle
1053 highlight_sample
1054
1055 Attributes updated:
1056 frame_color_set
1057 """
1058 # Set the color sample area.
1059 tag = self.theme_elements[self.highlight_target.get()][0]
1060 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1061 color = self.highlight_sample.tag_cget(tag, plane)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001062 self.style.configure('frame_color_set.TFrame', background=color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001063
1064 def paint_theme_sample(self):
1065 """Apply the theme colors to each element tag in the sample text.
1066
1067 Instance attributes accessed:
1068 theme_elements
1069 theme_source
1070 builtin_name
1071 custom_name
1072
1073 Attributes updated:
1074 highlight_sample: Set the tag elements to the theme.
1075
1076 Methods:
1077 set_color_sample
1078
1079 Called from:
1080 var_changed_builtin_name
1081 var_changed_custom_name
1082 load_theme_cfg
1083 """
1084 if self.theme_source.get(): # Default theme
1085 theme = self.builtin_name.get()
1086 else: # User theme
1087 theme = self.custom_name.get()
1088 for element_title in self.theme_elements:
1089 element = self.theme_elements[element_title][0]
1090 colors = idleConf.GetHighlight(theme, element)
1091 if element == 'cursor': # Cursor sample needs special painting.
1092 colors['background'] = idleConf.GetHighlight(
Terry Jan Reedyc1419572019-03-22 18:23:41 -04001093 theme, 'normal')['background']
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001094 # Handle any unsaved changes to this theme.
1095 if theme in changes['highlight']:
1096 theme_dict = changes['highlight'][theme]
1097 if element + '-foreground' in theme_dict:
1098 colors['foreground'] = theme_dict[element + '-foreground']
1099 if element + '-background' in theme_dict:
1100 colors['background'] = theme_dict[element + '-background']
1101 self.highlight_sample.tag_config(element, **colors)
1102 self.set_color_sample()
1103
1104 def save_new(self, theme_name, theme):
1105 """Save a newly created theme to idleConf.
1106
1107 theme_name - string, the name of the new theme
1108 theme - dictionary containing the new theme
1109 """
Cheryl Sabelladd023ad2020-01-27 17:15:56 -05001110 idleConf.userCfg['highlight'].AddSection(theme_name)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001111 for element in theme:
1112 value = theme[element]
1113 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
1114
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001115 def askyesno(self, *args, **kwargs):
1116 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001117 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001118
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001119 def delete_custom(self):
1120 """Handle event to delete custom theme.
1121
1122 The current theme is deactivated and the default theme is
1123 activated. The custom theme is permanently removed from
1124 the config file.
1125
1126 Attributes accessed:
1127 custom_name
1128
1129 Attributes updated:
1130 custom_theme_on
1131 customlist
1132 theme_source
1133 builtin_name
1134
1135 Methods:
1136 deactivate_current_config
1137 save_all_changed_extensions
1138 activate_config_changes
1139 set_theme_type
1140 """
1141 theme_name = self.custom_name.get()
1142 delmsg = 'Are you sure you wish to delete the theme %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001143 if not self.askyesno(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001144 'Delete Theme', delmsg % theme_name, parent=self):
1145 return
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001146 self.cd.deactivate_current_config()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001147 # Remove theme from changes, config, and file.
1148 changes.delete_section('highlight', theme_name)
1149 # Reload user theme list.
1150 item_list = idleConf.GetSectionList('user', 'highlight')
1151 item_list.sort()
1152 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001153 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001154 self.customlist.SetMenu(item_list, '- no custom themes -')
1155 else:
1156 self.customlist.SetMenu(item_list, item_list[0])
1157 # Revert to default theme.
1158 self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
1159 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
1160 # User can't back out of these changes, they must be applied now.
1161 changes.save_all()
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001162 self.extpage.save_all_changed_extensions()
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001163 self.cd.activate_config_changes()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001164 self.set_theme_type()
1165
1166
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001167class KeysPage(Frame):
1168
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001169 def __init__(self, master, extpage):
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001170 super().__init__(master)
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001171 self.extpage = extpage
Mark Rosemanc579ad12020-10-24 16:45:00 -07001172 self.cd = master.winfo_toplevel()
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001173 self.create_page_keys()
1174 self.load_key_cfg()
1175
1176 def create_page_keys(self):
1177 """Return frame of widgets for Keys tab.
1178
1179 Enable users to provisionally change both individual and sets of
1180 keybindings (shortcut keys). Except for features implemented as
1181 extensions, keybindings are stored in complete sets called
1182 keysets. Built-in keysets in idlelib/config-keys.def are fixed
1183 as far as the dialog is concerned. Any keyset can be used as the
1184 base for a new custom keyset, stored in .idlerc/config-keys.cfg.
1185
1186 Function load_key_cfg() initializes tk variables and keyset
1187 lists and calls load_keys_list for the current keyset.
1188 Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
1189 keyset_source, which controls if the current set of keybindings
1190 are from a builtin or custom keyset. DynOptionMenus builtinlist
1191 and customlist contain lists of the builtin and custom keysets,
1192 respectively, and the current item from each list is stored in
1193 vars builtin_name and custom_name.
1194
1195 Button delete_custom_keys invokes delete_custom_keys() to delete
1196 a custom keyset from idleConf.userCfg['keys'] and changes. Button
1197 save_custom_keys invokes save_as_new_key_set() which calls
1198 get_new_keys_name() and create_new_key_set() to save a custom keyset
1199 and its keybindings to idleConf.userCfg['keys'].
1200
1201 Listbox bindingslist contains all of the keybindings for the
1202 selected keyset. The keybindings are loaded in load_keys_list()
1203 and are pairs of (event, [keys]) where keys can be a list
1204 of one or more key combinations to bind to the same event.
1205 Mouse button 1 click invokes on_bindingslist_select(), which
1206 allows button_new_keys to be clicked.
1207
1208 So, an item is selected in listbindings, which activates
1209 button_new_keys, and clicking button_new_keys calls function
1210 get_new_keys(). Function get_new_keys() gets the key mappings from the
1211 current keyset for the binding event item that was selected. The
1212 function then displays another dialog, GetKeysDialog, with the
Cheryl Sabella82aff622017-08-17 20:39:00 -04001213 selected binding event and current keys and allows new key sequences
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001214 to be entered for that binding event. If the keys aren't
1215 changed, nothing happens. If the keys are changed and the keyset
1216 is a builtin, function get_new_keys_name() will be called
1217 for input of a custom keyset name. If no name is given, then the
1218 change to the keybinding will abort and no updates will be made. If
1219 a custom name is entered in the prompt or if the current keyset was
1220 already custom (and thus didn't require a prompt), then
1221 idleConf.userCfg['keys'] is updated in function create_new_key_set()
1222 with the change to the event binding. The item listing in bindingslist
1223 is updated with the new keys. Var keybinding is also set which invokes
1224 the callback function, var_changed_keybinding, to add the change to
1225 the 'keys' or 'extensions' changes tracker based on the binding type.
1226
1227 Tk Variables:
1228 keybinding: Action/key bindings.
1229
1230 Methods:
1231 load_keys_list: Reload active set.
1232 create_new_key_set: Combine active keyset and changes.
1233 set_keys_type: Command for keyset_source.
1234 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1235 deactivate_current_config: Remove keys bindings in editors.
1236
1237 Widgets for KeysPage(frame): (*) widgets bound to self
1238 frame_key_sets: LabelFrame
1239 frames[0]: Frame
1240 (*)builtin_keyset_on: Radiobutton - var keyset_source
1241 (*)custom_keyset_on: Radiobutton - var keyset_source
1242 (*)builtinlist: DynOptionMenu - var builtin_name,
1243 func keybinding_selected
1244 (*)customlist: DynOptionMenu - var custom_name,
1245 func keybinding_selected
1246 (*)keys_message: Label
1247 frames[1]: Frame
1248 (*)button_delete_custom_keys: Button - delete_custom_keys
1249 (*)button_save_custom_keys: Button - save_as_new_key_set
1250 frame_custom: LabelFrame
1251 frame_target: Frame
1252 target_title: Label
1253 scroll_target_y: Scrollbar
1254 scroll_target_x: Scrollbar
1255 (*)bindingslist: ListBox - on_bindingslist_select
1256 (*)button_new_keys: Button - get_new_keys & ..._name
1257 """
1258 self.builtin_name = tracers.add(
1259 StringVar(self), self.var_changed_builtin_name)
1260 self.custom_name = tracers.add(
1261 StringVar(self), self.var_changed_custom_name)
1262 self.keyset_source = tracers.add(
1263 BooleanVar(self), self.var_changed_keyset_source)
1264 self.keybinding = tracers.add(
1265 StringVar(self), self.var_changed_keybinding)
1266
1267 # Create widgets:
1268 # body and section frames.
1269 frame_custom = LabelFrame(
1270 self, borderwidth=2, relief=GROOVE,
1271 text=' Custom Key Bindings ')
1272 frame_key_sets = LabelFrame(
1273 self, borderwidth=2, relief=GROOVE, text=' Key Set ')
1274 # frame_custom.
1275 frame_target = Frame(frame_custom)
1276 target_title = Label(frame_target, text='Action - Key(s)')
1277 scroll_target_y = Scrollbar(frame_target)
1278 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1279 self.bindingslist = Listbox(
1280 frame_target, takefocus=FALSE, exportselection=FALSE)
1281 self.bindingslist.bind('<ButtonRelease-1>',
1282 self.on_bindingslist_select)
1283 scroll_target_y['command'] = self.bindingslist.yview
1284 scroll_target_x['command'] = self.bindingslist.xview
1285 self.bindingslist['yscrollcommand'] = scroll_target_y.set
1286 self.bindingslist['xscrollcommand'] = scroll_target_x.set
1287 self.button_new_keys = Button(
1288 frame_custom, text='Get New Keys for Selection',
Terry Jan Reedye8f7c782017-11-28 21:52:32 -05001289 command=self.get_new_keys, state='disabled')
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001290 # frame_key_sets.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001291 frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001292 for i in range(2)]
1293 self.builtin_keyset_on = Radiobutton(
1294 frames[0], variable=self.keyset_source, value=1,
1295 command=self.set_keys_type, text='Use a Built-in Key Set')
1296 self.custom_keyset_on = Radiobutton(
1297 frames[0], variable=self.keyset_source, value=0,
1298 command=self.set_keys_type, text='Use a Custom Key Set')
1299 self.builtinlist = DynOptionMenu(
1300 frames[0], self.builtin_name, None, command=None)
1301 self.customlist = DynOptionMenu(
1302 frames[0], self.custom_name, None, command=None)
1303 self.button_delete_custom_keys = Button(
1304 frames[1], text='Delete Custom Key Set',
1305 command=self.delete_custom_keys)
1306 self.button_save_custom_keys = Button(
1307 frames[1], text='Save as New Custom Key Set',
1308 command=self.save_as_new_key_set)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001309 self.keys_message = Label(frames[0], borderwidth=2)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001310
1311 # Pack widgets:
1312 # body.
1313 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1314 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1315 # frame_custom.
1316 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1317 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
1318 # frame_target.
1319 frame_target.columnconfigure(0, weight=1)
1320 frame_target.rowconfigure(1, weight=1)
1321 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1322 self.bindingslist.grid(row=1, column=0, sticky=NSEW)
1323 scroll_target_y.grid(row=1, column=1, sticky=NS)
1324 scroll_target_x.grid(row=2, column=0, sticky=EW)
1325 # frame_key_sets.
1326 self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
1327 self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
1328 self.builtinlist.grid(row=0, column=1, sticky=NSEW)
1329 self.customlist.grid(row=1, column=1, sticky=NSEW)
1330 self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
1331 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1332 self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1333 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1334 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
1335
1336 def load_key_cfg(self):
1337 "Load current configuration settings for the keybinding options."
1338 # Set current keys type radiobutton.
1339 self.keyset_source.set(idleConf.GetOption(
1340 'main', 'Keys', 'default', type='bool', default=1))
1341 # Set current keys.
1342 current_option = idleConf.CurrentKeys()
1343 # Load available keyset option menus.
1344 if self.keyset_source.get(): # Default theme selected.
1345 item_list = idleConf.GetSectionList('default', 'keys')
1346 item_list.sort()
1347 self.builtinlist.SetMenu(item_list, current_option)
1348 item_list = idleConf.GetSectionList('user', 'keys')
1349 item_list.sort()
1350 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001351 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001352 self.custom_name.set('- no custom keys -')
1353 else:
1354 self.customlist.SetMenu(item_list, item_list[0])
1355 else: # User key set selected.
1356 item_list = idleConf.GetSectionList('user', 'keys')
1357 item_list.sort()
1358 self.customlist.SetMenu(item_list, current_option)
1359 item_list = idleConf.GetSectionList('default', 'keys')
1360 item_list.sort()
1361 self.builtinlist.SetMenu(item_list, idleConf.default_keys())
1362 self.set_keys_type()
1363 # Load keyset element list.
1364 keyset_name = idleConf.CurrentKeys()
1365 self.load_keys_list(keyset_name)
1366
1367 def var_changed_builtin_name(self, *params):
1368 "Process selection of builtin key set."
1369 old_keys = (
1370 'IDLE Classic Windows',
1371 'IDLE Classic Unix',
1372 'IDLE Classic Mac',
1373 'IDLE Classic OSX',
1374 )
1375 value = self.builtin_name.get()
1376 if value not in old_keys:
1377 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1378 changes.add_option('main', 'Keys', 'name', old_keys[0])
1379 changes.add_option('main', 'Keys', 'name2', value)
1380 self.keys_message['text'] = 'New key set, see Help'
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001381 else:
1382 changes.add_option('main', 'Keys', 'name', value)
1383 changes.add_option('main', 'Keys', 'name2', '')
1384 self.keys_message['text'] = ''
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001385 self.load_keys_list(value)
1386
1387 def var_changed_custom_name(self, *params):
1388 "Process selection of custom key set."
1389 value = self.custom_name.get()
1390 if value != '- no custom keys -':
1391 changes.add_option('main', 'Keys', 'name', value)
1392 self.load_keys_list(value)
1393
1394 def var_changed_keyset_source(self, *params):
1395 "Process toggle between builtin key set and custom key set."
1396 value = self.keyset_source.get()
1397 changes.add_option('main', 'Keys', 'default', value)
1398 if value:
1399 self.var_changed_builtin_name()
1400 else:
1401 self.var_changed_custom_name()
1402
1403 def var_changed_keybinding(self, *params):
1404 "Store change to a keybinding."
1405 value = self.keybinding.get()
1406 key_set = self.custom_name.get()
1407 event = self.bindingslist.get(ANCHOR).split()[0]
1408 if idleConf.IsCoreBinding(event):
1409 changes.add_option('keys', key_set, event, value)
1410 else: # Event is an extension binding.
1411 ext_name = idleConf.GetExtnNameForEvent(event)
1412 ext_keybind_section = ext_name + '_cfgBindings'
1413 changes.add_option('extensions', ext_keybind_section, event, value)
1414
1415 def set_keys_type(self):
1416 "Set available screen options based on builtin or custom key set."
1417 if self.keyset_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -04001418 self.builtinlist['state'] = 'normal'
1419 self.customlist['state'] = 'disabled'
1420 self.button_delete_custom_keys.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001421 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001422 self.builtinlist['state'] = 'disabled'
1423 self.custom_keyset_on.state(('!disabled',))
1424 self.customlist['state'] = 'normal'
1425 self.button_delete_custom_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001426
1427 def get_new_keys(self):
1428 """Handle event to change key binding for selected line.
1429
1430 A selection of a key/binding in the list of current
1431 bindings pops up a dialog to enter a new binding. If
1432 the current key set is builtin and a binding has
1433 changed, then a name for a custom key set needs to be
1434 entered for the change to be applied.
1435 """
1436 list_index = self.bindingslist.index(ANCHOR)
1437 binding = self.bindingslist.get(list_index)
1438 bind_name = binding.split()[0]
1439 if self.keyset_source.get():
1440 current_key_set_name = self.builtin_name.get()
1441 else:
1442 current_key_set_name = self.custom_name.get()
1443 current_bindings = idleConf.GetCurrentKeySet()
1444 if current_key_set_name in changes['keys']: # unsaved changes
1445 key_set_changes = changes['keys'][current_key_set_name]
1446 for event in key_set_changes:
1447 current_bindings[event] = key_set_changes[event].split()
1448 current_key_sequences = list(current_bindings.values())
1449 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1450 current_key_sequences).result
1451 if new_keys:
1452 if self.keyset_source.get(): # Current key set is a built-in.
1453 message = ('Your changes will be saved as a new Custom Key Set.'
1454 ' Enter a name for your new Custom Key Set below.')
1455 new_keyset = self.get_new_keys_name(message)
1456 if not new_keyset: # User cancelled custom key set creation.
1457 self.bindingslist.select_set(list_index)
1458 self.bindingslist.select_anchor(list_index)
1459 return
1460 else: # Create new custom key set based on previously active key set.
1461 self.create_new_key_set(new_keyset)
1462 self.bindingslist.delete(list_index)
1463 self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
1464 self.bindingslist.select_set(list_index)
1465 self.bindingslist.select_anchor(list_index)
1466 self.keybinding.set(new_keys)
1467 else:
1468 self.bindingslist.select_set(list_index)
1469 self.bindingslist.select_anchor(list_index)
1470
1471 def get_new_keys_name(self, message):
1472 "Return new key set name from query popup."
1473 used_names = (idleConf.GetSectionList('user', 'keys') +
1474 idleConf.GetSectionList('default', 'keys'))
1475 new_keyset = SectionName(
1476 self, 'New Custom Key Set', message, used_names).result
1477 return new_keyset
1478
1479 def save_as_new_key_set(self):
1480 "Prompt for name of new key set and save changes using that name."
1481 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1482 if new_keys_name:
1483 self.create_new_key_set(new_keys_name)
1484
1485 def on_bindingslist_select(self, event):
1486 "Activate button to assign new keys to selected action."
Cheryl Sabella7028e592017-08-26 14:26:02 -04001487 self.button_new_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001488
1489 def create_new_key_set(self, new_key_set_name):
1490 """Create a new custom key set with the given name.
1491
1492 Copy the bindings/keys from the previously active keyset
1493 to the new keyset and activate the new custom keyset.
1494 """
1495 if self.keyset_source.get():
1496 prev_key_set_name = self.builtin_name.get()
1497 else:
1498 prev_key_set_name = self.custom_name.get()
1499 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1500 new_keys = {}
1501 for event in prev_keys: # Add key set to changed items.
1502 event_name = event[2:-2] # Trim off the angle brackets.
1503 binding = ' '.join(prev_keys[event])
1504 new_keys[event_name] = binding
1505 # Handle any unsaved changes to prev key set.
1506 if prev_key_set_name in changes['keys']:
1507 key_set_changes = changes['keys'][prev_key_set_name]
1508 for event in key_set_changes:
1509 new_keys[event] = key_set_changes[event]
1510 # Save the new key set.
1511 self.save_new_key_set(new_key_set_name, new_keys)
1512 # Change GUI over to the new key set.
1513 custom_key_list = idleConf.GetSectionList('user', 'keys')
1514 custom_key_list.sort()
1515 self.customlist.SetMenu(custom_key_list, new_key_set_name)
1516 self.keyset_source.set(0)
1517 self.set_keys_type()
1518
1519 def load_keys_list(self, keyset_name):
1520 """Reload the list of action/key binding pairs for the active key set.
1521
1522 An action/key binding can be selected to change the key binding.
1523 """
1524 reselect = False
1525 if self.bindingslist.curselection():
1526 reselect = True
1527 list_index = self.bindingslist.index(ANCHOR)
1528 keyset = idleConf.GetKeySet(keyset_name)
1529 bind_names = list(keyset.keys())
1530 bind_names.sort()
1531 self.bindingslist.delete(0, END)
1532 for bind_name in bind_names:
1533 key = ' '.join(keyset[bind_name])
1534 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1535 if keyset_name in changes['keys']:
1536 # Handle any unsaved changes to this key set.
1537 if bind_name in changes['keys'][keyset_name]:
1538 key = changes['keys'][keyset_name][bind_name]
1539 self.bindingslist.insert(END, bind_name+' - '+key)
1540 if reselect:
1541 self.bindingslist.see(list_index)
1542 self.bindingslist.select_set(list_index)
1543 self.bindingslist.select_anchor(list_index)
1544
1545 @staticmethod
1546 def save_new_key_set(keyset_name, keyset):
1547 """Save a newly created core key set.
1548
1549 Add keyset to idleConf.userCfg['keys'], not to disk.
1550 If the keyset doesn't exist, it is created. The
1551 binding/keys are taken from the keyset argument.
1552
1553 keyset_name - string, the name of the new key set
1554 keyset - dictionary containing the new keybindings
1555 """
Cheryl Sabelladd023ad2020-01-27 17:15:56 -05001556 idleConf.userCfg['keys'].AddSection(keyset_name)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001557 for event in keyset:
1558 value = keyset[event]
1559 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1560
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001561 def askyesno(self, *args, **kwargs):
1562 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001563 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001564
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001565 def delete_custom_keys(self):
1566 """Handle event to delete a custom key set.
1567
1568 Applying the delete deactivates the current configuration and
1569 reverts to the default. The custom key set is permanently
1570 deleted from the config file.
1571 """
1572 keyset_name = self.custom_name.get()
1573 delmsg = 'Are you sure you wish to delete the key set %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001574 if not self.askyesno(
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001575 'Delete Key Set', delmsg % keyset_name, parent=self):
1576 return
1577 self.cd.deactivate_current_config()
1578 # Remove key set from changes, config, and file.
1579 changes.delete_section('keys', keyset_name)
1580 # Reload user key set list.
1581 item_list = idleConf.GetSectionList('user', 'keys')
1582 item_list.sort()
1583 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001584 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001585 self.customlist.SetMenu(item_list, '- no custom keys -')
1586 else:
1587 self.customlist.SetMenu(item_list, item_list[0])
1588 # Revert to default key set.
1589 self.keyset_source.set(idleConf.defaultCfg['main']
Tal Einat604e7b92018-09-25 15:10:14 +03001590 .Get('Keys', 'default'))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001591 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Tal Einat604e7b92018-09-25 15:10:14 +03001592 or idleConf.default_keys())
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001593 # User can't back out of these changes, they must be applied now.
1594 changes.save_all()
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001595 self.extpage.save_all_changed_extensions()
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001596 self.cd.activate_config_changes()
1597 self.set_keys_type()
1598
1599
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001600class WinPage(Frame):
csabellae8eb17b2017-07-30 18:39:17 -04001601
csabella6f446be2017-08-01 00:24:07 -04001602 def __init__(self, master):
1603 super().__init__(master)
Tal Einat1ebee372019-07-23 13:02:11 +03001604
1605 self.init_validators()
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001606 self.create_page_windows()
1607 self.load_windows_cfg()
csabellae8eb17b2017-07-30 18:39:17 -04001608
Tal Einat1ebee372019-07-23 13:02:11 +03001609 def init_validators(self):
1610 digits_or_empty_re = re.compile(r'[0-9]*')
1611 def is_digits_or_empty(s):
1612 "Return 's is blank or contains only digits'"
1613 return digits_or_empty_re.fullmatch(s) is not None
1614 self.digits_only = (self.register(is_digits_or_empty), '%P',)
1615
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001616 def create_page_windows(self):
1617 """Return frame of widgets for Windows tab.
csabellae8eb17b2017-07-30 18:39:17 -04001618
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001619 Enable users to provisionally change general window options.
1620 Function load_windows_cfg initializes tk variables idleConf.
1621 Radiobuttons startup_shell_on and startup_editor_on set var
1622 startup_edit. Entry boxes win_width_int and win_height_int set var
1623 win_width and win_height. Setting var_name invokes the default
1624 callback that adds option to changes.
csabellae8eb17b2017-07-30 18:39:17 -04001625
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001626 Widgets for WinPage(Frame): (*) widgets bound to self
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001627 frame_window: LabelFrame
1628 frame_run: Frame
1629 startup_title: Label
1630 (*)startup_editor_on: Radiobutton - startup_edit
1631 (*)startup_shell_on: Radiobutton - startup_edit
1632 frame_win_size: Frame
1633 win_size_title: Label
1634 win_width_title: Label
1635 (*)win_width_int: Entry - win_width
1636 win_height_title: Label
1637 (*)win_height_int: Entry - win_height
Zackery Spytz9c284492019-11-13 00:13:33 -07001638 frame_cursor_blink: Frame
1639 cursor_blink_title: Label
1640 (*)cursor_blink_bool: Checkbutton - cursor_blink
Cheryl Sabella845d8642018-02-04 18:15:21 -05001641 frame_autocomplete: Frame
1642 auto_wait_title: Label
1643 (*)auto_wait_int: Entry - autocomplete_wait
1644 frame_paren1: Frame
1645 paren_style_title: Label
1646 (*)paren_style_type: OptionMenu - paren_style
1647 frame_paren2: Frame
1648 paren_time_title: Label
1649 (*)paren_flash_time: Entry - flash_delay
1650 (*)bell_on: Checkbutton - paren_bell
Cheryl Sabella845d8642018-02-04 18:15:21 -05001651 frame_format: Frame
1652 format_width_title: Label
1653 (*)format_width_int: Entry - format_width
csabellae8eb17b2017-07-30 18:39:17 -04001654 """
wohlganger58fc71c2017-09-10 16:19:47 -05001655 # Integer values need StringVar because int('') raises.
csabellae8eb17b2017-07-30 18:39:17 -04001656 self.startup_edit = tracers.add(
1657 IntVar(self), ('main', 'General', 'editor-on-startup'))
csabellae8eb17b2017-07-30 18:39:17 -04001658 self.win_width = tracers.add(
1659 StringVar(self), ('main', 'EditorWindow', 'width'))
1660 self.win_height = tracers.add(
1661 StringVar(self), ('main', 'EditorWindow', 'height'))
Zackery Spytz9c284492019-11-13 00:13:33 -07001662 self.cursor_blink = tracers.add(
1663 BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
wohlganger58fc71c2017-09-10 16:19:47 -05001664 self.autocomplete_wait = tracers.add(
1665 StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
1666 self.paren_style = tracers.add(
1667 StringVar(self), ('extensions', 'ParenMatch', 'style'))
1668 self.flash_delay = tracers.add(
1669 StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
1670 self.paren_bell = tracers.add(
1671 BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
wohlganger58fc71c2017-09-10 16:19:47 -05001672 self.format_width = tracers.add(
1673 StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
wohlganger58fc71c2017-09-10 16:19:47 -05001674
1675 # Create widgets:
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001676 frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
1677 text=' Window Preferences')
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001678
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001679 frame_run = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001680 startup_title = Label(frame_run, text='At Startup')
1681 self.startup_editor_on = Radiobutton(
1682 frame_run, variable=self.startup_edit, value=1,
1683 text="Open Edit Window")
1684 self.startup_shell_on = Radiobutton(
1685 frame_run, variable=self.startup_edit, value=0,
1686 text='Open Shell Window')
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001687
wohlganger58fc71c2017-09-10 16:19:47 -05001688 frame_win_size = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001689 win_size_title = Label(
1690 frame_win_size, text='Initial Window Size (in characters)')
1691 win_width_title = Label(frame_win_size, text='Width')
1692 self.win_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001693 frame_win_size, textvariable=self.win_width, width=3,
1694 validatecommand=self.digits_only, validate='key',
1695 )
csabellae8eb17b2017-07-30 18:39:17 -04001696 win_height_title = Label(frame_win_size, text='Height')
1697 self.win_height_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001698 frame_win_size, textvariable=self.win_height, width=3,
1699 validatecommand=self.digits_only, validate='key',
1700 )
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001701
Zackery Spytz9c284492019-11-13 00:13:33 -07001702 frame_cursor_blink = Frame(frame_window, borderwidth=0)
1703 cursor_blink_title = Label(frame_cursor_blink, text='Cursor Blink')
1704 self.cursor_blink_bool = Checkbutton(frame_cursor_blink,
1705 variable=self.cursor_blink, width=1)
1706
wohlganger58fc71c2017-09-10 16:19:47 -05001707 frame_autocomplete = Frame(frame_window, borderwidth=0,)
1708 auto_wait_title = Label(frame_autocomplete,
1709 text='Completions Popup Wait (milliseconds)')
1710 self.auto_wait_int = Entry(frame_autocomplete, width=6,
Tal Einat1ebee372019-07-23 13:02:11 +03001711 textvariable=self.autocomplete_wait,
1712 validatecommand=self.digits_only,
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001713 validate='key')
wohlganger58fc71c2017-09-10 16:19:47 -05001714
1715 frame_paren1 = Frame(frame_window, borderwidth=0)
1716 paren_style_title = Label(frame_paren1, text='Paren Match Style')
1717 self.paren_style_type = OptionMenu(
1718 frame_paren1, self.paren_style, 'expression',
1719 "opener","parens","expression")
1720 frame_paren2 = Frame(frame_window, borderwidth=0)
1721 paren_time_title = Label(
1722 frame_paren2, text='Time Match Displayed (milliseconds)\n'
1723 '(0 is until next input)')
1724 self.paren_flash_time = Entry(
1725 frame_paren2, textvariable=self.flash_delay, width=6)
1726 self.bell_on = Checkbutton(
1727 frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001728 frame_format = Frame(frame_window, borderwidth=0)
wohlganger58fc71c2017-09-10 16:19:47 -05001729 format_width_title = Label(frame_format,
1730 text='Format Paragraph Max Width')
1731 self.format_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001732 frame_format, textvariable=self.format_width, width=4,
1733 validatecommand=self.digits_only, validate='key',
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001734 )
wohlganger58fc71c2017-09-10 16:19:47 -05001735
csabellae8eb17b2017-07-30 18:39:17 -04001736 # Pack widgets:
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001737 frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
csabellae8eb17b2017-07-30 18:39:17 -04001738 # frame_run.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001739 frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04001740 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1741 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1742 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
csabellae8eb17b2017-07-30 18:39:17 -04001743 # frame_win_size.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001744 frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04001745 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1746 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
1747 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
1748 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
1749 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Zackery Spytz9c284492019-11-13 00:13:33 -07001750 # frame_cursor_blink.
1751 frame_cursor_blink.pack(side=TOP, padx=5, pady=0, fill=X)
1752 cursor_blink_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1753 self.cursor_blink_bool.pack(side=LEFT, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05001754 # frame_autocomplete.
1755 frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
1756 auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1757 self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
1758 # frame_paren.
1759 frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
1760 paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1761 self.paren_style_type.pack(side=TOP, padx=10, pady=5)
1762 frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
1763 paren_time_title.pack(side=LEFT, anchor=W, padx=5)
1764 self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
1765 self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05001766 # frame_format.
1767 frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
1768 format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1769 self.format_width_int.pack(side=TOP, padx=10, pady=5)
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07001770
1771 def load_windows_cfg(self):
wohlganger58fc71c2017-09-10 16:19:47 -05001772 # Set variables for all windows.
csabellae8eb17b2017-07-30 18:39:17 -04001773 self.startup_edit.set(idleConf.GetOption(
wohlganger58fc71c2017-09-10 16:19:47 -05001774 'main', 'General', 'editor-on-startup', type='bool'))
csabellae8eb17b2017-07-30 18:39:17 -04001775 self.win_width.set(idleConf.GetOption(
1776 'main', 'EditorWindow', 'width', type='int'))
1777 self.win_height.set(idleConf.GetOption(
1778 'main', 'EditorWindow', 'height', type='int'))
Zackery Spytz9c284492019-11-13 00:13:33 -07001779 self.cursor_blink.set(idleConf.GetOption(
1780 'main', 'EditorWindow', 'cursor-blink', type='bool'))
wohlganger58fc71c2017-09-10 16:19:47 -05001781 self.autocomplete_wait.set(idleConf.GetOption(
1782 'extensions', 'AutoComplete', 'popupwait', type='int'))
1783 self.paren_style.set(idleConf.GetOption(
1784 'extensions', 'ParenMatch', 'style'))
1785 self.flash_delay.set(idleConf.GetOption(
1786 'extensions', 'ParenMatch', 'flash-delay', type='int'))
1787 self.paren_bell.set(idleConf.GetOption(
1788 'extensions', 'ParenMatch', 'bell'))
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001789 self.format_width.set(idleConf.GetOption(
1790 'extensions', 'FormatParagraph', 'max-width', type='int'))
1791
1792
1793class ShedPage(Frame):
1794
1795 def __init__(self, master):
1796 super().__init__(master)
1797
1798 self.init_validators()
1799 self.create_page_shed()
1800 self.load_shelled_cfg()
1801
1802 def init_validators(self):
1803 digits_or_empty_re = re.compile(r'[0-9]*')
1804 def is_digits_or_empty(s):
1805 "Return 's is blank or contains only digits'"
1806 return digits_or_empty_re.fullmatch(s) is not None
1807 self.digits_only = (self.register(is_digits_or_empty), '%P',)
1808
1809 def create_page_shed(self):
1810 """Return frame of widgets for Shell/Ed tab.
1811
1812 Enable users to provisionally change shell and editor options.
1813 Function load_shed_cfg initializes tk variables using idleConf.
1814 Entry box auto_squeeze_min_lines_int sets
1815 auto_squeeze_min_lines_int. Setting var_name invokes the
1816 default callback that adds option to changes.
1817
1818 Widgets for ShedPage(Frame): (*) widgets bound to self
1819 frame_shell: LabelFrame
1820 frame_auto_squeeze_min_lines: Frame
1821 auto_squeeze_min_lines_title: Label
1822 (*)auto_squeeze_min_lines_int: Entry -
1823 auto_squeeze_min_lines
1824 frame_editor: LabelFrame
1825 frame_save: Frame
1826 run_save_title: Label
1827 (*)save_ask_on: Radiobutton - autosave
1828 (*)save_auto_on: Radiobutton - autosave
1829 frame_format: Frame
1830 format_width_title: Label
1831 (*)format_width_int: Entry - format_width
1832 frame_line_numbers_default: Frame
1833 line_numbers_default_title: Label
1834 (*)line_numbers_default_bool: Checkbutton - line_numbers_default
1835 frame_context: Frame
1836 context_title: Label
1837 (*)context_int: Entry - context_lines
1838 """
1839 # Integer values need StringVar because int('') raises.
1840 self.auto_squeeze_min_lines = tracers.add(
1841 StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
1842
1843 self.autosave = tracers.add(
1844 IntVar(self), ('main', 'General', 'autosave'))
1845 self.line_numbers_default = tracers.add(
1846 BooleanVar(self),
1847 ('main', 'EditorWindow', 'line-numbers-default'))
1848 self.context_lines = tracers.add(
1849 StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
1850
1851 # Create widgets:
1852 frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
1853 text=' Shell Preferences')
1854 frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
1855 text=' Editor Preferences')
1856 # Frame_shell.
1857 frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
1858 auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
1859 text='Auto-Squeeze Min. Lines:')
1860 self.auto_squeeze_min_lines_int = Entry(
1861 frame_auto_squeeze_min_lines, width=4,
1862 textvariable=self.auto_squeeze_min_lines,
1863 validatecommand=self.digits_only, validate='key',
1864 )
1865 # Frame_editor.
1866 frame_save = Frame(frame_editor, borderwidth=0)
1867 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
1868
1869 self.save_ask_on = Radiobutton(
1870 frame_save, variable=self.autosave, value=0,
1871 text="Prompt to Save")
1872 self.save_auto_on = Radiobutton(
1873 frame_save, variable=self.autosave, value=1,
1874 text='No Prompt')
1875
1876 frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
1877 line_numbers_default_title = Label(
1878 frame_line_numbers_default, text='Show line numbers in new windows')
1879 self.line_numbers_default_bool = Checkbutton(
1880 frame_line_numbers_default,
1881 variable=self.line_numbers_default,
1882 width=1)
1883
1884 frame_context = Frame(frame_editor, borderwidth=0)
1885 context_title = Label(frame_context, text='Max Context Lines :')
1886 self.context_int = Entry(
1887 frame_context, textvariable=self.context_lines, width=3,
1888 validatecommand=self.digits_only, validate='key',
1889 )
1890
1891 # Pack widgets:
1892 frame_shell.pack(side=TOP, padx=5, pady=5, fill=BOTH)
1893 Label(self).pack() # Spacer -- better solution?
1894 frame_editor.pack(side=TOP, padx=5, pady=5, fill=BOTH)
1895 # frame_auto_squeeze_min_lines
1896 frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
1897 auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1898 self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
1899 # frame_save.
1900 frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
1901 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1902 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1903 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1904 # frame_line_numbers_default.
1905 frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
1906 line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1907 self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
1908 # frame_context.
1909 frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
1910 context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1911 self.context_int.pack(side=TOP, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05001912
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07001913 def load_shelled_cfg(self):
Miss Islington (bot)664ae292021-06-09 13:37:56 -07001914 # Set variables for shell windows.
1915 self.auto_squeeze_min_lines.set(idleConf.GetOption(
1916 'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
wohlganger58fc71c2017-09-10 16:19:47 -05001917 # Set variables for editor windows.
1918 self.autosave.set(idleConf.GetOption(
1919 'main', 'General', 'autosave', default=0, type='bool'))
Tal Einat7123ea02019-07-23 15:22:11 +03001920 self.line_numbers_default.set(idleConf.GetOption(
1921 'main', 'EditorWindow', 'line-numbers-default', type='bool'))
wohlganger58fc71c2017-09-10 16:19:47 -05001922 self.context_lines.set(idleConf.GetOption(
Cheryl Sabella29996a12018-06-01 19:23:00 -04001923 'extensions', 'CodeContext', 'maxlines', type='int'))
wohlganger58fc71c2017-09-10 16:19:47 -05001924
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07001925
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001926class ExtPage(Frame):
1927 def __init__(self, master):
1928 super().__init__(master)
1929 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1930 self.ext_userCfg = idleConf.userCfg['extensions']
1931 self.is_int = self.register(is_int)
1932 self.load_extensions()
1933 self.create_page_extensions() # Requires extension names.
1934
1935 def create_page_extensions(self):
1936 """Configure IDLE feature extensions and help menu extensions.
1937
1938 List the feature extensions and a configuration box for the
1939 selected extension. Help menu extensions are in a HelpFrame.
1940
1941 This code reads the current configuration using idleConf,
1942 supplies a GUI interface to change the configuration values,
1943 and saves the changes using idleConf.
1944
1945 Some changes may require restarting IDLE. This depends on each
1946 extension's implementation.
1947
1948 All values are treated as text, and it is up to the user to
1949 supply reasonable values. The only exception to this are the
1950 'enable*' options, which are boolean, and can be toggled with a
1951 True/False button.
1952
1953 Methods:
1954 extension_selected: Handle selection from list.
1955 create_extension_frame: Hold widgets for one extension.
1956 set_extension_value: Set in userCfg['extensions'].
1957 save_all_changed_extensions: Call extension page Save().
1958 """
1959 self.extension_names = StringVar(self)
1960
1961 frame_ext = LabelFrame(self, borderwidth=2, relief=GROOVE,
1962 text=' Feature Extensions ')
1963 self.frame_help = HelpFrame(self, borderwidth=2, relief=GROOVE,
1964 text=' Help Menu Extensions ')
1965
1966 frame_ext.rowconfigure(0, weight=1)
1967 frame_ext.columnconfigure(2, weight=1)
1968 self.extension_list = Listbox(frame_ext, listvariable=self.extension_names,
1969 selectmode='browse')
1970 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1971 scroll = Scrollbar(frame_ext, command=self.extension_list.yview)
1972 self.extension_list.yscrollcommand=scroll.set
1973 self.details_frame = LabelFrame(frame_ext, width=250, height=250)
1974 self.extension_list.grid(column=0, row=0, sticky='nws')
1975 scroll.grid(column=1, row=0, sticky='ns')
1976 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1977 frame_ext.configure(padding=10)
1978 self.config_frame = {}
1979 self.current_extension = None
1980
1981 self.outerframe = self # TEMPORARY
1982 self.tabbed_page_set = self.extension_list # TEMPORARY
1983
1984 # Create the frame holding controls for each extension.
1985 ext_names = ''
1986 for ext_name in sorted(self.extensions):
1987 self.create_extension_frame(ext_name)
1988 ext_names = ext_names + '{' + ext_name + '} '
1989 self.extension_names.set(ext_names)
1990 self.extension_list.selection_set(0)
1991 self.extension_selected(None)
1992
1993
1994 frame_ext.grid(row=0, column=0, sticky='nsew')
1995 Label(self).grid(row=1, column=0) # Spacer. Replace with config?
1996 self.frame_help.grid(row=2, column=0, sticky='sew')
1997
1998 def load_extensions(self):
1999 "Fill self.extensions with data from the default and user configs."
2000 self.extensions = {}
2001 for ext_name in idleConf.GetExtensions(active_only=False):
2002 # Former built-in extensions are already filtered out.
2003 self.extensions[ext_name] = []
2004
2005 for ext_name in self.extensions:
2006 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
2007
2008 # Bring 'enable' options to the beginning of the list.
2009 enables = [opt_name for opt_name in opt_list
2010 if opt_name.startswith('enable')]
2011 for opt_name in enables:
2012 opt_list.remove(opt_name)
2013 opt_list = enables + opt_list
2014
2015 for opt_name in opt_list:
2016 def_str = self.ext_defaultCfg.Get(
2017 ext_name, opt_name, raw=True)
2018 try:
2019 def_obj = {'True':True, 'False':False}[def_str]
2020 opt_type = 'bool'
2021 except KeyError:
2022 try:
2023 def_obj = int(def_str)
2024 opt_type = 'int'
2025 except ValueError:
2026 def_obj = def_str
2027 opt_type = None
2028 try:
2029 value = self.ext_userCfg.Get(
2030 ext_name, opt_name, type=opt_type, raw=True,
2031 default=def_obj)
2032 except ValueError: # Need this until .Get fixed.
2033 value = def_obj # Bad values overwritten by entry.
2034 var = StringVar(self)
2035 var.set(str(value))
2036
2037 self.extensions[ext_name].append({'name': opt_name,
2038 'type': opt_type,
2039 'default': def_str,
2040 'value': value,
2041 'var': var,
2042 })
2043
2044 def extension_selected(self, event):
2045 "Handle selection of an extension from the list."
2046 newsel = self.extension_list.curselection()
2047 if newsel:
2048 newsel = self.extension_list.get(newsel)
2049 if newsel is None or newsel != self.current_extension:
2050 if self.current_extension:
2051 self.details_frame.config(text='')
2052 self.config_frame[self.current_extension].grid_forget()
2053 self.current_extension = None
2054 if newsel:
2055 self.details_frame.config(text=newsel)
2056 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
2057 self.current_extension = newsel
2058
2059 def create_extension_frame(self, ext_name):
2060 """Create a frame holding the widgets to configure one extension"""
2061 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
2062 self.config_frame[ext_name] = f
2063 entry_area = f.interior
2064 # Create an entry for each configuration option.
2065 for row, opt in enumerate(self.extensions[ext_name]):
2066 # Create a row with a label and entry/checkbutton.
2067 label = Label(entry_area, text=opt['name'])
2068 label.grid(row=row, column=0, sticky=NW)
2069 var = opt['var']
2070 if opt['type'] == 'bool':
2071 Checkbutton(entry_area, variable=var,
2072 onvalue='True', offvalue='False', width=8
2073 ).grid(row=row, column=1, sticky=W, padx=7)
2074 elif opt['type'] == 'int':
2075 Entry(entry_area, textvariable=var, validate='key',
2076 validatecommand=(self.is_int, '%P'), width=10
2077 ).grid(row=row, column=1, sticky=NSEW, padx=7)
2078
2079 else: # type == 'str'
2080 # Limit size to fit non-expanding space with larger font.
2081 Entry(entry_area, textvariable=var, width=15
2082 ).grid(row=row, column=1, sticky=NSEW, padx=7)
2083 return
2084
2085 def set_extension_value(self, section, opt):
2086 """Return True if the configuration was added or changed.
2087
2088 If the value is the same as the default, then remove it
2089 from user config file.
2090 """
2091 name = opt['name']
2092 default = opt['default']
2093 value = opt['var'].get().strip() or default
2094 opt['var'].set(value)
2095 # if self.defaultCfg.has_section(section):
2096 # Currently, always true; if not, indent to return.
2097 if (value == default):
2098 return self.ext_userCfg.RemoveOption(section, name)
2099 # Set the option.
2100 return self.ext_userCfg.SetOption(section, name, value)
2101
2102 def save_all_changed_extensions(self):
2103 """Save configuration changes to the user config file.
2104
2105 Attributes accessed:
2106 extensions
2107
2108 Methods:
2109 set_extension_value
2110 """
2111 has_changes = False
2112 for ext_name in self.extensions:
2113 options = self.extensions[ext_name]
2114 for opt in options:
2115 if self.set_extension_value(ext_name, opt):
2116 has_changes = True
2117 if has_changes:
2118 self.ext_userCfg.Save()
2119
2120
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07002121class HelpFrame(LabelFrame):
2122
2123 def __init__(self, master, **cfg):
2124 super().__init__(master, **cfg)
2125 self.create_frame_help()
2126 self.load_helplist()
2127
2128 def create_frame_help(self):
2129 """Create LabelFrame for additional help menu sources.
2130
2131 load_helplist loads list user_helplist with
2132 name, position pairs and copies names to listbox helplist.
2133 Clicking a name invokes help_source selected. Clicking
2134 button_helplist_name invokes helplist_item_name, which also
2135 changes user_helplist. These functions all call
2136 set_add_delete_state. All but load call update_help_changes to
2137 rewrite changes['main']['HelpFiles'].
2138
2139 Widgets for HelpFrame(LabelFrame): (*) widgets bound to self
2140 frame_helplist: Frame
2141 (*)helplist: ListBox
2142 scroll_helplist: Scrollbar
2143 frame_buttons: Frame
2144 (*)button_helplist_edit
2145 (*)button_helplist_add
2146 (*)button_helplist_remove
2147 """
2148 # self = frame_help in dialog (until ExtPage class).
2149 frame_helplist = Frame(self)
2150 self.helplist = Listbox(
2151 frame_helplist, height=5, takefocus=True,
2152 exportselection=FALSE)
2153 scroll_helplist = Scrollbar(frame_helplist)
2154 scroll_helplist['command'] = self.helplist.yview
2155 self.helplist['yscrollcommand'] = scroll_helplist.set
2156 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
2157
2158 frame_buttons = Frame(self)
2159 self.button_helplist_edit = Button(
2160 frame_buttons, text='Edit', state='disabled',
2161 width=8, command=self.helplist_item_edit)
2162 self.button_helplist_add = Button(
2163 frame_buttons, text='Add',
2164 width=8, command=self.helplist_item_add)
2165 self.button_helplist_remove = Button(
2166 frame_buttons, text='Remove', state='disabled',
2167 width=8, command=self.helplist_item_remove)
2168
2169 # Pack frame_help.
2170 frame_helplist.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
2171 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
2172 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
2173 frame_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
2174 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
2175 self.button_helplist_add.pack(side=TOP, anchor=W)
2176 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
csabellae8eb17b2017-07-30 18:39:17 -04002177
2178 def help_source_selected(self, event):
2179 "Handle event for selecting additional help."
2180 self.set_add_delete_state()
2181
2182 def set_add_delete_state(self):
2183 "Toggle the state for the help list buttons based on list entries."
2184 if self.helplist.size() < 1: # No entries in list.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002185 self.button_helplist_edit.state(('disabled',))
2186 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002187 else: # Some entries.
2188 if self.helplist.curselection(): # There currently is a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002189 self.button_helplist_edit.state(('!disabled',))
2190 self.button_helplist_remove.state(('!disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002191 else: # There currently is not a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002192 self.button_helplist_edit.state(('disabled',))
2193 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002194
2195 def helplist_item_add(self):
2196 """Handle add button for the help list.
2197
2198 Query for name and location of new help sources and add
2199 them to the list.
2200 """
2201 help_source = HelpSource(self, 'New Help Source').result
2202 if help_source:
2203 self.user_helplist.append(help_source)
2204 self.helplist.insert(END, help_source[0])
2205 self.update_help_changes()
2206
2207 def helplist_item_edit(self):
2208 """Handle edit button for the help list.
2209
2210 Query with existing help source information and update
2211 config if the values are changed.
2212 """
2213 item_index = self.helplist.index(ANCHOR)
2214 help_source = self.user_helplist[item_index]
2215 new_help_source = HelpSource(
2216 self, 'Edit Help Source',
2217 menuitem=help_source[0],
2218 filepath=help_source[1],
2219 ).result
2220 if new_help_source and new_help_source != help_source:
2221 self.user_helplist[item_index] = new_help_source
2222 self.helplist.delete(item_index)
2223 self.helplist.insert(item_index, new_help_source[0])
2224 self.update_help_changes()
2225 self.set_add_delete_state() # Selected will be un-selected
2226
2227 def helplist_item_remove(self):
2228 """Handle remove button for the help list.
2229
2230 Delete the help list item from config.
2231 """
2232 item_index = self.helplist.index(ANCHOR)
2233 del(self.user_helplist[item_index])
2234 self.helplist.delete(item_index)
2235 self.update_help_changes()
2236 self.set_add_delete_state()
2237
2238 def update_help_changes(self):
2239 "Clear and rebuild the HelpFiles section in changes"
2240 changes['main']['HelpFiles'] = {}
2241 for num in range(1, len(self.user_helplist) + 1):
2242 changes.add_option(
2243 'main', 'HelpFiles', str(num),
2244 ';'.join(self.user_helplist[num-1][:2]))
2245
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07002246 def load_helplist(self):
2247 # Set additional help sources.
2248 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
2249 self.helplist.delete(0, 'end')
2250 for help_item in self.user_helplist:
2251 self.helplist.insert(END, help_item[0])
2252 self.set_add_delete_state()
2253
csabellae8eb17b2017-07-30 18:39:17 -04002254
csabella45bf7232017-07-26 19:09:58 -04002255class VarTrace:
2256 """Maintain Tk variables trace state."""
2257
2258 def __init__(self):
2259 """Store Tk variables and callbacks.
2260
2261 untraced: List of tuples (var, callback)
2262 that do not have the callback attached
2263 to the Tk var.
2264 traced: List of tuples (var, callback) where
2265 that callback has been attached to the var.
2266 """
2267 self.untraced = []
2268 self.traced = []
2269
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002270 def clear(self):
2271 "Clear lists (for tests)."
Terry Jan Reedy733d0f62017-08-07 14:22:44 -04002272 # Call after all tests in a module to avoid memory leaks.
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002273 self.untraced.clear()
2274 self.traced.clear()
2275
csabella45bf7232017-07-26 19:09:58 -04002276 def add(self, var, callback):
2277 """Add (var, callback) tuple to untraced list.
2278
2279 Args:
2280 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04002281 callback: Either function name to be used as a callback
2282 or a tuple with IdleConf config-type, section, and
2283 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04002284
2285 Return:
2286 Tk variable instance.
2287 """
2288 if isinstance(callback, tuple):
2289 callback = self.make_callback(var, callback)
2290 self.untraced.append((var, callback))
2291 return var
2292
2293 @staticmethod
2294 def make_callback(var, config):
2295 "Return default callback function to add values to changes instance."
2296 def default_callback(*params):
2297 "Add config values to changes instance."
2298 changes.add_option(*config, var.get())
2299 return default_callback
2300
2301 def attach(self):
2302 "Attach callback to all vars that are not traced."
2303 while self.untraced:
2304 var, callback = self.untraced.pop()
2305 var.trace_add('write', callback)
2306 self.traced.append((var, callback))
2307
2308 def detach(self):
2309 "Remove callback from traced vars."
2310 while self.traced:
2311 var, callback = self.traced.pop()
2312 var.trace_remove('write', var.trace_info()[0][1])
2313 self.untraced.append((var, callback))
2314
2315
csabella5b591542017-07-28 14:40:59 -04002316tracers = VarTrace()
2317
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002318help_common = '''\
2319When you click either the Apply or Ok buttons, settings in this
2320dialog that are different from IDLE's default are saved in
2321a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002322these changes apply to all versions of IDLE installed on this
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002323machine. [Cancel] only cancels changes made since the last save.
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002324'''
2325help_pages = {
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002326 'Fonts/Tabs':'''
2327Font sample: This shows what a selection of Basic Multilingual Plane
2328unicode characters look like for the current font selection. If the
2329selected font does not define a character, Tk attempts to find another
2330font that does. Substitute glyphs depend on what is available on a
2331particular system and will not necessarily have the same size as the
2332font selected. Line contains 20 characters up to Devanagari, 14 for
2333Tamil, and 10 for East Asia.
2334
2335Hebrew and Arabic letters should display right to left, starting with
2336alef, \u05d0 and \u0627. Arabic digits display left to right. The
2337Devanagari and Tamil lines start with digits. The East Asian lines
2338are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
2339Hiragana and Katakana.
Serhiy Storchakaed6554c2017-10-28 03:22:44 +03002340
2341You can edit the font sample. Changes remain until IDLE is closed.
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002342''',
Cheryl Sabella3866d9b2017-09-10 22:41:10 -04002343 'Highlights': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002344Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002345The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002346be used with older IDLE releases if it is saved as a custom
2347theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04002348''',
2349 'Keys': '''
2350Keys:
2351The IDLE Modern Unix key set is new in June 2016. It can only
2352be used with older IDLE releases if it is saved as a custom
2353key set, with a different name.
2354''',
wohlganger58fc71c2017-09-10 16:19:47 -05002355 'General': '''
2356General:
wohlgangerfae2c352017-06-27 21:36:23 -05002357
penguindustin96466302019-05-06 14:57:17 -04002358AutoComplete: Popupwait is milliseconds to wait after key char, without
wohlgangerfae2c352017-06-27 21:36:23 -05002359cursor movement, before popping up completion box. Key char is '.' after
2360identifier or a '/' (or '\\' on Windows) within a string.
2361
2362FormatParagraph: Max-width is max chars in lines after re-formatting.
2363Use with paragraphs in both strings and comment blocks.
2364
2365ParenMatch: Style indicates what is highlighted when closer is entered:
2366'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
2367'expression' (default) - also everything in between. Flash-delay is how
2368long to highlight if cursor is not moved (0 means forever).
Cheryl Sabella29996a12018-06-01 19:23:00 -04002369
2370CodeContext: Maxlines is the maximum number of code context lines to
2371display when Code Context is turned on for an editor window.
Tal Einat604e7b92018-09-25 15:10:14 +03002372
2373Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
2374of output to automatically "squeeze".
Cheryl Sabellae40e2a22021-01-05 02:26:43 -05002375''',
2376 'Extensions': '''
2377ZzDummy: This extension is provided as an example for how to create and
2378use an extension. Enable indicates whether the extension is active or
2379not; likewise enable_editor and enable_shell indicate which windows it
2380will be active on. For this extension, z-text is the text that will be
2381inserted at or removed from the beginning of the lines of selected text,
2382or the current line if no selection.
2383''',
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002384}
2385
Steven M. Gavac11ccf32001-09-24 09:43:17 +00002386
Terry Jan Reedy93f35422015-10-13 22:03:51 -04002387def is_int(s):
2388 "Return 's is blank or represents an int'"
2389 if not s:
2390 return True
2391 try:
2392 int(s)
2393 return True
2394 except ValueError:
2395 return False
2396
2397
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002398class VerticalScrolledFrame(Frame):
2399 """A pure Tkinter vertically scrollable frame.
2400
2401 * Use the 'interior' attribute to place widgets inside the scrollable frame
2402 * Construct and pack/place/grid normally
2403 * This frame only allows vertical scrolling
2404 """
2405 def __init__(self, parent, *args, **kw):
2406 Frame.__init__(self, parent, *args, **kw)
2407
csabella7eb58832017-07-04 21:30:58 -04002408 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002409 vscrollbar = Scrollbar(self, orient=VERTICAL)
2410 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
Cheryl Sabella7028e592017-08-26 14:26:02 -04002411 canvas = Canvas(self, borderwidth=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04002412 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002413 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
2414 vscrollbar.config(command=canvas.yview)
2415
csabella7eb58832017-07-04 21:30:58 -04002416 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002417 canvas.xview_moveto(0)
2418 canvas.yview_moveto(0)
2419
csabella7eb58832017-07-04 21:30:58 -04002420 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002421 self.interior = interior = Frame(canvas)
2422 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
2423
csabella7eb58832017-07-04 21:30:58 -04002424 # Track changes to the canvas and frame width and sync them,
2425 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002426 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04002427 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002428 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
2429 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002430 interior.bind('<Configure>', _configure_interior)
2431
2432 def _configure_canvas(event):
2433 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04002434 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002435 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
2436 canvas.bind('<Configure>', _configure_canvas)
2437
2438 return
2439
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002440
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00002441if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -04002442 from unittest import main
2443 main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
2444
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04002445 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04002446 run(ConfigDialog)