blob: 9c0153b1a73611aa56395d3fe54e65ba4f5a59ee [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)
csabellae8eb17b2017-07-30 18:39:17 -0400119 self.genpage = GenPage(note)
csabella9397e2a2017-07-30 13:34:25 -0400120 note.add(self.fontpage, text='Fonts/Tabs')
121 note.add(self.highpage, text='Highlights')
122 note.add(self.keyspage, text=' Keys ')
123 note.add(self.genpage, text=' General ')
124 note.add(self.extpage, text='Extensions')
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400125 note.enable_traversal()
126 note.pack(side=TOP, expand=TRUE, fill=BOTH)
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400127 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400128
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400129 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400130 """Return frame of action buttons for dialog.
131
132 Methods:
133 ok
134 apply
135 cancel
136 help
137
138 Widget Structure:
139 outer: Frame
140 buttons: Frame
141 (no assignment): Button (ok)
142 (no assignment): Button (apply)
143 (no assignment): Button (cancel)
144 (no assignment): Button (help)
145 (no assignment): Frame
146 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400147 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400148 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400149 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400150 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000151 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400152 padding_args = {'padding': (6, 3)}
Mark Rosemanc579ad12020-10-24 16:45:00 -0700153 outer = Frame(self.frame, padding=2)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500154 buttons_frame = Frame(outer, padding=2)
155 self.buttons = {}
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400156 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400157 ('Ok', self.ok),
158 ('Apply', self.apply),
159 ('Cancel', self.cancel),
160 ('Help', self.help)):
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500161 self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
162 takefocus=FALSE, **padding_args)
163 self.buttons[txt].pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400164 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400165 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500166 buttons_frame.pack(side=BOTTOM)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400167 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400168
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400169 def ok(self):
170 """Apply config changes, then dismiss dialog.
171
172 Methods:
173 apply
174 destroy: inherited
175 """
176 self.apply()
177 self.destroy()
178
179 def apply(self):
180 """Apply config changes and leave dialog open.
181
182 Methods:
183 deactivate_current_config
184 save_all_changed_extensions
185 activate_config_changes
186 """
187 self.deactivate_current_config()
188 changes.save_all()
189 self.save_all_changed_extensions()
190 self.activate_config_changes()
191
192 def cancel(self):
193 """Dismiss config dialog.
194
195 Methods:
196 destroy: inherited
197 """
Cheryl Sabellad0d9fa82020-01-25 04:00:54 -0500198 changes.clear()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400199 self.destroy()
200
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300201 def destroy(self):
202 global font_sample_text
203 font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
Tal Einat10ea9402018-08-02 09:18:29 +0300204 self.grab_release()
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300205 super().destroy()
206
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400207 def help(self):
208 """Create textview for config dialog help.
209
Xtreakd9677f32019-06-03 09:51:15 +0530210 Attributes accessed:
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400211 note
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400212 Methods:
213 view_text: Method from textview module.
214 """
Cheryl Sabella3866d9b2017-09-10 22:41:10 -0400215 page = self.note.tab(self.note.select(), option='text').strip()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400216 view_text(self, title='Help for IDLE preferences',
Zackery Spytz2e43b642020-01-22 20:54:30 -0700217 contents=help_common+help_pages.get(page, ''))
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400218
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400219 def deactivate_current_config(self):
220 """Remove current key bindings.
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400221 Iterate over window instances defined in parent and remove
222 the keybindings.
223 """
224 # Before a config is saved, some cleanup of current
225 # config must be done - remove the previous keybindings.
226 win_instances = self.parent.instance_dict.keys()
227 for instance in win_instances:
228 instance.RemoveKeybindings()
229
230 def activate_config_changes(self):
231 """Apply configuration changes to current windows.
232
233 Dynamically update the current parent window instances
234 with some of the configuration changes.
235 """
236 win_instances = self.parent.instance_dict.keys()
237 for instance in win_instances:
238 instance.ResetColorizer()
239 instance.ResetFont()
240 instance.set_notabs_indentwidth()
241 instance.ApplyKeybindings()
242 instance.reset_help_menu_entries()
Zackery Spytz9c284492019-11-13 00:13:33 -0700243 instance.update_cursor_blink()
Terry Jan Reedy5777ecc2017-09-16 01:42:28 -0400244 for klass in reloadables:
245 klass.reload()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400246
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400247
csabella6f446be2017-08-01 00:24:07 -0400248# class TabPage(Frame): # A template for Page classes.
249# def __init__(self, master):
250# super().__init__(master)
251# self.create_page_tab()
252# self.load_tab_cfg()
253# def create_page_tab(self):
254# # Define tk vars and register var and callback with tracers.
255# # Create subframes and widgets.
256# # Pack widgets.
257# def load_tab_cfg(self):
258# # Initialize widgets with data from idleConf.
259# def var_changed_var_name():
260# # For each tk var that needs other than default callback.
261# def other_methods():
262# # Define tab-specific behavior.
263
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300264font_sample_text = (
265 '<ASCII/Latin1>\n'
266 'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
267 '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
268 '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
269 '\n<IPA,Greek,Cyrillic>\n'
270 '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
271 '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
272 '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
273 '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
274 '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
275 '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
276 '\n<Hebrew, Arabic>\n'
277 '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
278 '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
279 '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
280 '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
281 '\n<Devanagari, Tamil>\n'
282 '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
283 '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
284 '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
285 '\u0b85\u0b87\u0b89\u0b8e\n'
286 '\n<East Asian>\n'
287 '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
288 '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
289 '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
290 '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
291 )
292
csabella6f446be2017-08-01 00:24:07 -0400293
csabella9397e2a2017-07-30 13:34:25 -0400294class FontPage(Frame):
295
csabella6f446be2017-08-01 00:24:07 -0400296 def __init__(self, master, highpage):
297 super().__init__(master)
csabella9397e2a2017-07-30 13:34:25 -0400298 self.highlight_sample = highpage.highlight_sample
299 self.create_page_font_tab()
300 self.load_font_cfg()
301 self.load_tab_cfg()
302
303 def create_page_font_tab(self):
304 """Return frame of widgets for Font/Tabs tab.
305
306 Fonts: Enable users to provisionally change font face, size, or
307 boldness and to see the consequence of proposed choices. Each
308 action set 3 options in changes structuree and changes the
309 corresponding aspect of the font sample on this page and
310 highlight sample on highlight page.
311
312 Function load_font_cfg initializes font vars and widgets from
313 idleConf entries and tk.
314
315 Fontlist: mouse button 1 click or up or down key invoke
316 on_fontlist_select(), which sets var font_name.
317
318 Sizelist: clicking the menubutton opens the dropdown menu. A
319 mouse button 1 click or return key sets var font_size.
320
321 Bold_toggle: clicking the box toggles var font_bold.
322
323 Changing any of the font vars invokes var_changed_font, which
324 adds all 3 font options to changes and calls set_samples.
325 Set_samples applies a new font constructed from the font vars to
Leo Ariasc3d95082018-02-03 18:36:10 -0600326 font_sample and to highlight_sample on the highlight page.
csabella9397e2a2017-07-30 13:34:25 -0400327
328 Tabs: Enable users to change spaces entered for indent tabs.
329 Changing indent_scale value with the mouse sets Var space_num,
330 which invokes the default callback to add an entry to
331 changes. Load_tab_cfg initializes space_num to default.
332
Cheryl Sabella2f896462017-08-14 21:21:43 -0400333 Widgets for FontPage(Frame): (*) widgets bound to self
334 frame_font: LabelFrame
335 frame_font_name: Frame
336 font_name_title: Label
337 (*)fontlist: ListBox - font_name
338 scroll_font: Scrollbar
339 frame_font_param: Frame
340 font_size_title: Label
341 (*)sizelist: DynOptionMenu - font_size
342 (*)bold_toggle: Checkbutton - font_bold
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400343 frame_sample: LabelFrame
344 (*)font_sample: Label
Cheryl Sabella2f896462017-08-14 21:21:43 -0400345 frame_indent: LabelFrame
346 indent_title: Label
347 (*)indent_scale: Scale - space_num
csabella9397e2a2017-07-30 13:34:25 -0400348 """
csabella6f446be2017-08-01 00:24:07 -0400349 self.font_name = tracers.add(StringVar(self), self.var_changed_font)
350 self.font_size = tracers.add(StringVar(self), self.var_changed_font)
351 self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
csabella9397e2a2017-07-30 13:34:25 -0400352 self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
353
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400354 # Define frames and widgets.
csabella9397e2a2017-07-30 13:34:25 -0400355 frame_font = LabelFrame(
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400356 self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ')
357 frame_sample = LabelFrame(
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300358 self, borderwidth=2, relief=GROOVE,
359 text=' Font Sample (Editable) ')
csabella9397e2a2017-07-30 13:34:25 -0400360 frame_indent = LabelFrame(
csabella6f446be2017-08-01 00:24:07 -0400361 self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
csabella9397e2a2017-07-30 13:34:25 -0400362 # frame_font.
363 frame_font_name = Frame(frame_font)
364 frame_font_param = Frame(frame_font)
365 font_name_title = Label(
366 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400367 self.fontlist = Listbox(frame_font_name, height=15,
csabella9397e2a2017-07-30 13:34:25 -0400368 takefocus=True, exportselection=FALSE)
369 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
370 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
371 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
372 scroll_font = Scrollbar(frame_font_name)
373 scroll_font.config(command=self.fontlist.yview)
374 self.fontlist.config(yscrollcommand=scroll_font.set)
375 font_size_title = Label(frame_font_param, text='Size :')
376 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
377 self.bold_toggle = Checkbutton(
378 frame_font_param, variable=self.font_bold,
379 onvalue=1, offvalue=0, text='Bold')
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400380 # frame_sample.
Tal Einat3221a632019-07-27 19:57:48 +0300381 font_sample_frame = ScrollableTextFrame(frame_sample)
382 self.font_sample = font_sample_frame.text
383 self.font_sample.config(wrap=NONE, width=1, height=1)
Serhiy Storchakaed6554c2017-10-28 03:22:44 +0300384 self.font_sample.insert(END, font_sample_text)
csabella9397e2a2017-07-30 13:34:25 -0400385 # frame_indent.
386 indent_title = Label(
387 frame_indent, justify=LEFT,
388 text='Python Standard: 4 Spaces!')
389 self.indent_scale = Scale(
390 frame_indent, variable=self.space_num,
391 orient='horizontal', tickinterval=2, from_=2, to=16)
392
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400393 # Grid and pack widgets:
394 self.columnconfigure(1, weight=1)
Tal Einat3221a632019-07-27 19:57:48 +0300395 self.rowconfigure(2, weight=1)
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400396 frame_font.grid(row=0, column=0, padx=5, pady=5)
Tal Einat3221a632019-07-27 19:57:48 +0300397 frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400398 sticky='nsew')
399 frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
csabella9397e2a2017-07-30 13:34:25 -0400400 # frame_font.
401 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
402 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
403 font_name_title.pack(side=TOP, anchor=W)
404 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
405 scroll_font.pack(side=LEFT, fill=Y)
406 font_size_title.pack(side=LEFT, anchor=W)
407 self.sizelist.pack(side=LEFT, anchor=W)
408 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
Terry Jan Reedye2e42272017-10-17 18:56:16 -0400409 # frame_sample.
Tal Einat3221a632019-07-27 19:57:48 +0300410 font_sample_frame.pack(expand=TRUE, fill=BOTH)
csabella9397e2a2017-07-30 13:34:25 -0400411 # frame_indent.
csabella9397e2a2017-07-30 13:34:25 -0400412 indent_title.pack(side=TOP, anchor=W, padx=5)
413 self.indent_scale.pack(side=TOP, padx=5, fill=X)
414
csabella9397e2a2017-07-30 13:34:25 -0400415 def load_font_cfg(self):
416 """Load current configuration settings for the font options.
417
418 Retrieve current font with idleConf.GetFont and font families
419 from tk. Setup fontlist and set font_name. Setup sizelist,
420 which sets font_size. Set font_bold. Call set_samples.
421 """
422 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
423 font_name = configured_font[0].lower()
424 font_size = configured_font[1]
425 font_bold = configured_font[2]=='bold'
426
Terry Jan Reedy96ce2272020-02-10 20:08:58 -0500427 # Set sorted no-duplicate editor font selection list and font_name.
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500428 fonts = sorted(set(tkfont.families(self)))
csabella9397e2a2017-07-30 13:34:25 -0400429 for font in fonts:
430 self.fontlist.insert(END, font)
431 self.font_name.set(font_name)
432 lc_fonts = [s.lower() for s in fonts]
433 try:
434 current_font_index = lc_fonts.index(font_name)
435 self.fontlist.see(current_font_index)
436 self.fontlist.select_set(current_font_index)
437 self.fontlist.select_anchor(current_font_index)
438 self.fontlist.activate(current_font_index)
439 except ValueError:
440 pass
441 # Set font size dropdown.
442 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
443 '16', '18', '20', '22', '25', '29', '34', '40'),
444 font_size)
445 # Set font weight.
446 self.font_bold.set(font_bold)
447 self.set_samples()
448
449 def var_changed_font(self, *params):
450 """Store changes to font attributes.
451
452 When one font attribute changes, save them all, as they are
453 not independent from each other. In particular, when we are
454 overriding the default font, we need to write out everything.
455 """
456 value = self.font_name.get()
457 changes.add_option('main', 'EditorWindow', 'font', value)
458 value = self.font_size.get()
459 changes.add_option('main', 'EditorWindow', 'font-size', value)
460 value = self.font_bold.get()
461 changes.add_option('main', 'EditorWindow', 'font-bold', value)
462 self.set_samples()
463
464 def on_fontlist_select(self, event):
465 """Handle selecting a font from the list.
466
467 Event can result from either mouse click or Up or Down key.
468 Set font_name and example displays to selection.
469 """
470 font = self.fontlist.get(
471 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
472 self.font_name.set(font.lower())
473
474 def set_samples(self, event=None):
475 """Update update both screen samples with the font settings.
476
477 Called on font initialization and change events.
478 Accesses font_name, font_size, and font_bold Variables.
Leo Ariasc3d95082018-02-03 18:36:10 -0600479 Updates font_sample and highlight page highlight_sample.
csabella9397e2a2017-07-30 13:34:25 -0400480 """
481 font_name = self.font_name.get()
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500482 font_weight = tkfont.BOLD if self.font_bold.get() else tkfont.NORMAL
csabella9397e2a2017-07-30 13:34:25 -0400483 new_font = (font_name, self.font_size.get(), font_weight)
484 self.font_sample['font'] = new_font
485 self.highlight_sample['font'] = new_font
486
487 def load_tab_cfg(self):
488 """Load current configuration settings for the tab options.
489
490 Attributes updated:
491 space_num: Set to value from idleConf.
492 """
493 # Set indent sizes.
494 space_num = idleConf.GetOption(
495 'main', 'Indent', 'num-spaces', default=4, type='int')
496 self.space_num.set(space_num)
497
498 def var_changed_space_num(self, *params):
499 "Store change to indentation size."
500 value = self.space_num.get()
501 changes.add_option('main', 'Indent', 'num-spaces', value)
502
503
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400504class HighPage(Frame):
505
Miss Islington (bot)33a7a242021-06-08 19:11:26 -0700506 def __init__(self, master, extpage):
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400507 super().__init__(master)
Miss Islington (bot)33a7a242021-06-08 19:11:26 -0700508 self.extpage = extpage
Mark Rosemanc579ad12020-10-24 16:45:00 -0700509 self.cd = master.winfo_toplevel()
Cheryl Sabella7028e592017-08-26 14:26:02 -0400510 self.style = Style(master)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400511 self.create_page_highlight()
512 self.load_theme_cfg()
513
514 def create_page_highlight(self):
515 """Return frame of widgets for Highlighting tab.
516
517 Enable users to provisionally change foreground and background
518 colors applied to textual tags. Color mappings are stored in
519 complete listings called themes. Built-in themes in
520 idlelib/config-highlight.def are fixed as far as the dialog is
521 concerned. Any theme can be used as the base for a new custom
522 theme, stored in .idlerc/config-highlight.cfg.
523
524 Function load_theme_cfg() initializes tk variables and theme
525 lists and calls paint_theme_sample() and set_highlight_target()
526 for the current theme. Radiobuttons builtin_theme_on and
527 custom_theme_on toggle var theme_source, which controls if the
528 current set of colors are from a builtin or custom theme.
529 DynOptionMenus builtinlist and customlist contain lists of the
530 builtin and custom themes, respectively, and the current item
531 from each list is stored in vars builtin_name and custom_name.
532
533 Function paint_theme_sample() applies the colors from the theme
534 to the tags in text widget highlight_sample and then invokes
535 set_color_sample(). Function set_highlight_target() sets the state
536 of the radiobuttons fg_on and bg_on based on the tag and it also
537 invokes set_color_sample().
538
539 Function set_color_sample() sets the background color for the frame
540 holding the color selector. This provides a larger visual of the
541 color for the current tag and plane (foreground/background).
542
543 Note: set_color_sample() is called from many places and is often
544 called more than once when a change is made. It is invoked when
545 foreground or background is selected (radiobuttons), from
546 paint_theme_sample() (theme is changed or load_cfg is called), and
547 from set_highlight_target() (target tag is changed or load_cfg called).
548
549 Button delete_custom invokes delete_custom() to delete
550 a custom theme from idleConf.userCfg['highlight'] and changes.
551 Button save_custom invokes save_as_new_theme() which calls
552 get_new_theme_name() and create_new() to save a custom theme
553 and its colors to idleConf.userCfg['highlight'].
554
555 Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
556 if the current selected color for a tag is for the foreground or
557 background.
558
559 DynOptionMenu targetlist contains a readable description of the
560 tags applied to Python source within IDLE. Selecting one of the
561 tags from this list populates highlight_target, which has a callback
562 function set_highlight_target().
563
564 Text widget highlight_sample displays a block of text (which is
565 mock Python code) in which is embedded the defined tags and reflects
566 the color attributes of the current theme and changes for those tags.
567 Mouse button 1 allows for selection of a tag and updates
568 highlight_target with that tag value.
569
570 Note: The font in highlight_sample is set through the config in
571 the fonts tab.
572
573 In other words, a tag can be selected either from targetlist or
574 by clicking on the sample text within highlight_sample. The
575 plane (foreground/background) is selected via the radiobutton.
576 Together, these two (tag and plane) control what color is
577 shown in set_color_sample() for the current theme. Button set_color
578 invokes get_color() which displays a ColorChooser to change the
579 color for the selected tag/plane. If a new color is picked,
580 it will be saved to changes and the highlight_sample and
581 frame background will be updated.
582
583 Tk Variables:
584 color: Color of selected target.
585 builtin_name: Menu variable for built-in theme.
586 custom_name: Menu variable for custom theme.
587 fg_bg_toggle: Toggle for foreground/background color.
588 Note: this has no callback.
589 theme_source: Selector for built-in or custom theme.
590 highlight_target: Menu variable for the highlight tag target.
591
592 Instance Data Attributes:
593 theme_elements: Dictionary of tags for text highlighting.
594 The key is the display name and the value is a tuple of
595 (tag name, display sort order).
596
597 Methods [attachment]:
598 load_theme_cfg: Load current highlight colors.
599 get_color: Invoke colorchooser [button_set_color].
600 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
601 set_highlight_target: set fg_bg_toggle, set_color_sample().
602 set_color_sample: Set frame background to target.
603 on_new_color_set: Set new color and add option.
604 paint_theme_sample: Recolor sample.
605 get_new_theme_name: Get from popup.
606 create_new: Combine theme with changes and save.
607 save_as_new_theme: Save [button_save_custom].
608 set_theme_type: Command for [theme_source].
609 delete_custom: Activate default [button_delete_custom].
610 save_new: Save to userCfg['theme'] (is function).
611
612 Widgets of highlights page frame: (*) widgets bound to self
613 frame_custom: LabelFrame
614 (*)highlight_sample: Text
615 (*)frame_color_set: Frame
616 (*)button_set_color: Button
617 (*)targetlist: DynOptionMenu - highlight_target
618 frame_fg_bg_toggle: Frame
619 (*)fg_on: Radiobutton - fg_bg_toggle
620 (*)bg_on: Radiobutton - fg_bg_toggle
621 (*)button_save_custom: Button
622 frame_theme: LabelFrame
623 theme_type_title: Label
624 (*)builtin_theme_on: Radiobutton - theme_source
625 (*)custom_theme_on: Radiobutton - theme_source
626 (*)builtinlist: DynOptionMenu - builtin_name
627 (*)customlist: DynOptionMenu - custom_name
628 (*)button_delete_custom: Button
629 (*)theme_message: Label
630 """
631 self.theme_elements = {
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400632 'Normal Code or Text': ('normal', '00'),
Cheryl Sabellade651622018-06-01 21:45:54 -0400633 'Code Context': ('context', '01'),
634 'Python Keywords': ('keyword', '02'),
635 'Python Definitions': ('definition', '03'),
636 'Python Builtins': ('builtin', '04'),
637 'Python Comments': ('comment', '05'),
638 'Python Strings': ('string', '06'),
639 'Selected Text': ('hilite', '07'),
640 'Found Text': ('hit', '08'),
641 'Cursor': ('cursor', '09'),
642 'Editor Breakpoint': ('break', '10'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400643 'Shell Prompt': ('console', '11'),
644 'Error Text': ('error', '12'),
645 'Shell User Output': ('stdout', '13'),
646 'Shell User Exception': ('stderr', '14'),
Tal Einat7123ea02019-07-23 15:22:11 +0300647 'Line Number': ('linenumber', '16'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400648 }
649 self.builtin_name = tracers.add(
650 StringVar(self), self.var_changed_builtin_name)
651 self.custom_name = tracers.add(
652 StringVar(self), self.var_changed_custom_name)
653 self.fg_bg_toggle = BooleanVar(self)
654 self.color = tracers.add(
655 StringVar(self), self.var_changed_color)
656 self.theme_source = tracers.add(
657 BooleanVar(self), self.var_changed_theme_source)
658 self.highlight_target = tracers.add(
659 StringVar(self), self.var_changed_highlight_target)
660
661 # Create widgets:
662 # body frame and section frames.
663 frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
664 text=' Custom Highlighting ')
665 frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
666 text=' Highlighting Theme ')
667 # frame_custom.
Tal Einat3221a632019-07-27 19:57:48 +0300668 sample_frame = ScrollableTextFrame(
669 frame_custom, relief=SOLID, borderwidth=1)
670 text = self.highlight_sample = sample_frame.text
671 text.configure(
672 font=('courier', 12, ''), cursor='hand2', width=1, height=1,
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400673 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Cheryl Sabelladd023ad2020-01-27 17:15:56 -0500674 # Prevent perhaps invisible selection of word or slice.
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400675 text.bind('<Double-Button-1>', lambda e: 'break')
676 text.bind('<B1-Motion>', lambda e: 'break')
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400677 string_tags=(
678 ('# Click selects item.', 'comment'), ('\n', 'normal'),
679 ('code context section', 'context'), ('\n', 'normal'),
680 ('| cursor', 'cursor'), ('\n', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400681 ('def', 'keyword'), (' ', 'normal'),
682 ('func', 'definition'), ('(param):\n ', 'normal'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400683 ('"Return None."', 'string'), ('\n var0 = ', 'normal'),
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400684 ("'string'", 'string'), ('\n var1 = ', 'normal'),
685 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
686 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
687 ('list', 'builtin'), ('(', 'normal'),
688 ('None', 'keyword'), (')\n', 'normal'),
689 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedyb2229552019-07-28 12:04:31 -0400690 ('>>>', 'console'), (' 3.14**2\n', 'normal'),
691 ('9.8596', 'stdout'), ('\n', 'normal'),
692 ('>>>', 'console'), (' pri ', 'normal'),
693 ('n', 'error'), ('t(\n', 'normal'),
694 ('SyntaxError', 'stderr'), ('\n', 'normal'))
695 for string, tag in string_tags:
696 text.insert(END, string, tag)
Tal Einat7123ea02019-07-23 15:22:11 +0300697 n_lines = len(text.get('1.0', END).splitlines())
Tal Einat3221a632019-07-27 19:57:48 +0300698 for lineno in range(1, n_lines):
Tal Einat7123ea02019-07-23 15:22:11 +0300699 text.insert(f'{lineno}.0',
700 f'{lineno:{len(str(n_lines))}d} ',
701 'linenumber')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400702 for element in self.theme_elements:
703 def tem(event, elem=element):
Cheryl Sabella8f7a7982017-08-19 22:04:40 -0400704 # event.widget.winfo_top_level().highlight_target.set(elem)
705 self.highlight_target.set(elem)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400706 text.tag_bind(
707 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400708 text['state'] = 'disabled'
709 self.style.configure('frame_color_set.TFrame', borderwidth=1,
710 relief='solid')
711 self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400712 frame_fg_bg_toggle = Frame(frame_custom)
713 self.button_set_color = Button(
714 self.frame_color_set, text='Choose Color for :',
Cheryl Sabella7028e592017-08-26 14:26:02 -0400715 command=self.get_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400716 self.targetlist = DynOptionMenu(
717 self.frame_color_set, self.highlight_target, None,
718 highlightthickness=0) #, command=self.set_highlight_targetBinding
719 self.fg_on = Radiobutton(
720 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
721 text='Foreground', command=self.set_color_sample_binding)
722 self.bg_on = Radiobutton(
723 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
724 text='Background', command=self.set_color_sample_binding)
725 self.fg_bg_toggle.set(1)
726 self.button_save_custom = Button(
727 frame_custom, text='Save as New Custom Theme',
728 command=self.save_as_new_theme)
729 # frame_theme.
730 theme_type_title = Label(frame_theme, text='Select : ')
731 self.builtin_theme_on = Radiobutton(
732 frame_theme, variable=self.theme_source, value=1,
733 command=self.set_theme_type, text='a Built-in Theme')
734 self.custom_theme_on = Radiobutton(
735 frame_theme, variable=self.theme_source, value=0,
736 command=self.set_theme_type, text='a Custom Theme')
737 self.builtinlist = DynOptionMenu(
738 frame_theme, self.builtin_name, None, command=None)
739 self.customlist = DynOptionMenu(
740 frame_theme, self.custom_name, None, command=None)
741 self.button_delete_custom = Button(
742 frame_theme, text='Delete Custom Theme',
743 command=self.delete_custom)
Cheryl Sabella7028e592017-08-26 14:26:02 -0400744 self.theme_message = Label(frame_theme, borderwidth=2)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400745 # Pack widgets:
746 # body.
747 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
wohlganger58fc71c2017-09-10 16:19:47 -0500748 frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400749 # frame_custom.
Tal Einat3221a632019-07-27 19:57:48 +0300750 self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400751 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Tal Einat3221a632019-07-27 19:57:48 +0300752 sample_frame.pack(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400753 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
754 self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
755 self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
756 self.fg_on.pack(side=LEFT, anchor=E)
757 self.bg_on.pack(side=RIGHT, anchor=W)
758 self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
759 # frame_theme.
760 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
761 self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
762 self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
763 self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
764 self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
765 self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
766 self.theme_message.pack(side=TOP, fill=X, pady=5)
767
768 def load_theme_cfg(self):
769 """Load current configuration settings for the theme options.
770
771 Based on the theme_source toggle, the theme is set as
772 either builtin or custom and the initial widget values
773 reflect the current settings from idleConf.
774
775 Attributes updated:
776 theme_source: Set from idleConf.
777 builtinlist: List of default themes from idleConf.
778 customlist: List of custom themes from idleConf.
779 custom_theme_on: Disabled if there are no custom themes.
780 custom_theme: Message with additional information.
781 targetlist: Create menu from self.theme_elements.
782
783 Methods:
784 set_theme_type
785 paint_theme_sample
786 set_highlight_target
787 """
788 # Set current theme type radiobutton.
789 self.theme_source.set(idleConf.GetOption(
790 'main', 'Theme', 'default', type='bool', default=1))
791 # Set current theme.
792 current_option = idleConf.CurrentTheme()
793 # Load available theme option menus.
794 if self.theme_source.get(): # Default theme selected.
795 item_list = idleConf.GetSectionList('default', 'highlight')
796 item_list.sort()
797 self.builtinlist.SetMenu(item_list, current_option)
798 item_list = idleConf.GetSectionList('user', 'highlight')
799 item_list.sort()
800 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400801 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400802 self.custom_name.set('- no custom themes -')
803 else:
804 self.customlist.SetMenu(item_list, item_list[0])
805 else: # User theme selected.
806 item_list = idleConf.GetSectionList('user', 'highlight')
807 item_list.sort()
808 self.customlist.SetMenu(item_list, current_option)
809 item_list = idleConf.GetSectionList('default', 'highlight')
810 item_list.sort()
811 self.builtinlist.SetMenu(item_list, item_list[0])
812 self.set_theme_type()
813 # Load theme element option menu.
814 theme_names = list(self.theme_elements.keys())
815 theme_names.sort(key=lambda x: self.theme_elements[x][1])
816 self.targetlist.SetMenu(theme_names, theme_names[0])
817 self.paint_theme_sample()
818 self.set_highlight_target()
819
820 def var_changed_builtin_name(self, *params):
821 """Process new builtin theme selection.
822
823 Add the changed theme's name to the changed_items and recreate
824 the sample with the values from the selected theme.
825 """
826 old_themes = ('IDLE Classic', 'IDLE New')
827 value = self.builtin_name.get()
828 if value not in old_themes:
829 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
830 changes.add_option('main', 'Theme', 'name', old_themes[0])
831 changes.add_option('main', 'Theme', 'name2', value)
832 self.theme_message['text'] = 'New theme, see Help'
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400833 else:
834 changes.add_option('main', 'Theme', 'name', value)
835 changes.add_option('main', 'Theme', 'name2', '')
836 self.theme_message['text'] = ''
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400837 self.paint_theme_sample()
838
839 def var_changed_custom_name(self, *params):
840 """Process new custom theme selection.
841
842 If a new custom theme is selected, add the name to the
843 changed_items and apply the theme to the sample.
844 """
845 value = self.custom_name.get()
846 if value != '- no custom themes -':
847 changes.add_option('main', 'Theme', 'name', value)
848 self.paint_theme_sample()
849
850 def var_changed_theme_source(self, *params):
851 """Process toggle between builtin and custom theme.
852
853 Update the default toggle value and apply the newly
854 selected theme type.
855 """
856 value = self.theme_source.get()
857 changes.add_option('main', 'Theme', 'default', value)
858 if value:
859 self.var_changed_builtin_name()
860 else:
861 self.var_changed_custom_name()
862
863 def var_changed_color(self, *params):
864 "Process change to color choice."
865 self.on_new_color_set()
866
867 def var_changed_highlight_target(self, *params):
868 "Process selection of new target tag for highlighting."
869 self.set_highlight_target()
870
871 def set_theme_type(self):
872 """Set available screen options based on builtin or custom theme.
873
874 Attributes accessed:
875 theme_source
876
877 Attributes updated:
878 builtinlist
879 customlist
880 button_delete_custom
881 custom_theme_on
882
883 Called from:
884 handler for builtin_theme_on and custom_theme_on
885 delete_custom
886 create_new
887 load_theme_cfg
888 """
889 if self.theme_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -0400890 self.builtinlist['state'] = 'normal'
891 self.customlist['state'] = 'disabled'
892 self.button_delete_custom.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400893 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -0400894 self.builtinlist['state'] = 'disabled'
895 self.custom_theme_on.state(('!disabled',))
896 self.customlist['state'] = 'normal'
897 self.button_delete_custom.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400898
899 def get_color(self):
900 """Handle button to select a new color for the target tag.
901
902 If a new color is selected while using a builtin theme, a
903 name must be supplied to create a custom theme.
904
905 Attributes accessed:
906 highlight_target
907 frame_color_set
908 theme_source
909
910 Attributes updated:
911 color
912
913 Methods:
914 get_new_theme_name
915 create_new
916 """
917 target = self.highlight_target.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -0400918 prev_color = self.style.lookup(self.frame_color_set['style'],
919 'background')
Terry Jan Reedy879986d2021-01-25 06:33:18 -0500920 rgbTuplet, color_string = colorchooser.askcolor(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400921 parent=self, title='Pick new color for : '+target,
922 initialcolor=prev_color)
923 if color_string and (color_string != prev_color):
924 # User didn't cancel and they chose a new color.
925 if self.theme_source.get(): # Current theme is a built-in.
926 message = ('Your changes will be saved as a new Custom Theme. '
927 'Enter a name for your new Custom Theme below.')
928 new_theme = self.get_new_theme_name(message)
929 if not new_theme: # User cancelled custom theme creation.
930 return
931 else: # Create new custom theme based on previously active theme.
932 self.create_new(new_theme)
933 self.color.set(color_string)
934 else: # Current theme is user defined.
935 self.color.set(color_string)
936
937 def on_new_color_set(self):
938 "Display sample of new color selection on the dialog."
939 new_color = self.color.get()
Cheryl Sabella7028e592017-08-26 14:26:02 -0400940 self.style.configure('frame_color_set.TFrame', background=new_color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -0400941 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
942 sample_element = self.theme_elements[self.highlight_target.get()][0]
943 self.highlight_sample.tag_config(sample_element, **{plane: new_color})
944 theme = self.custom_name.get()
945 theme_element = sample_element + '-' + plane
946 changes.add_option('highlight', theme, theme_element, new_color)
947
948 def get_new_theme_name(self, message):
949 "Return name of new theme from query popup."
950 used_names = (idleConf.GetSectionList('user', 'highlight') +
951 idleConf.GetSectionList('default', 'highlight'))
952 new_theme = SectionName(
953 self, 'New Custom Theme', message, used_names).result
954 return new_theme
955
956 def save_as_new_theme(self):
957 """Prompt for new theme name and create the theme.
958
959 Methods:
960 get_new_theme_name
961 create_new
962 """
963 new_theme_name = self.get_new_theme_name('New Theme Name:')
964 if new_theme_name:
965 self.create_new(new_theme_name)
966
967 def create_new(self, new_theme_name):
968 """Create a new custom theme with the given name.
969
970 Create the new theme based on the previously active theme
971 with the current changes applied. Once it is saved, then
972 activate the new theme.
973
974 Attributes accessed:
975 builtin_name
976 custom_name
977
978 Attributes updated:
979 customlist
980 theme_source
981
982 Method:
983 save_new
984 set_theme_type
985 """
986 if self.theme_source.get():
987 theme_type = 'default'
988 theme_name = self.builtin_name.get()
989 else:
990 theme_type = 'user'
991 theme_name = self.custom_name.get()
992 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
993 # Apply any of the old theme's unsaved changes to the new theme.
994 if theme_name in changes['highlight']:
995 theme_changes = changes['highlight'][theme_name]
996 for element in theme_changes:
997 new_theme[element] = theme_changes[element]
998 # Save the new theme.
999 self.save_new(new_theme_name, new_theme)
1000 # Change GUI over to the new theme.
1001 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
1002 custom_theme_list.sort()
1003 self.customlist.SetMenu(custom_theme_list, new_theme_name)
1004 self.theme_source.set(0)
1005 self.set_theme_type()
1006
1007 def set_highlight_target(self):
1008 """Set fg/bg toggle and color based on highlight tag target.
1009
1010 Instance variables accessed:
1011 highlight_target
1012
1013 Attributes updated:
1014 fg_on
1015 bg_on
1016 fg_bg_toggle
1017
1018 Methods:
1019 set_color_sample
1020
1021 Called from:
1022 var_changed_highlight_target
1023 load_theme_cfg
1024 """
1025 if self.highlight_target.get() == 'Cursor': # bg not possible
Cheryl Sabella7028e592017-08-26 14:26:02 -04001026 self.fg_on.state(('disabled',))
1027 self.bg_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001028 self.fg_bg_toggle.set(1)
1029 else: # Both fg and bg can be set.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001030 self.fg_on.state(('!disabled',))
1031 self.bg_on.state(('!disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001032 self.fg_bg_toggle.set(1)
1033 self.set_color_sample()
1034
1035 def set_color_sample_binding(self, *args):
1036 """Change color sample based on foreground/background toggle.
1037
1038 Methods:
1039 set_color_sample
1040 """
1041 self.set_color_sample()
1042
1043 def set_color_sample(self):
1044 """Set the color of the frame background to reflect the selected target.
1045
1046 Instance variables accessed:
1047 theme_elements
1048 highlight_target
1049 fg_bg_toggle
1050 highlight_sample
1051
1052 Attributes updated:
1053 frame_color_set
1054 """
1055 # Set the color sample area.
1056 tag = self.theme_elements[self.highlight_target.get()][0]
1057 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1058 color = self.highlight_sample.tag_cget(tag, plane)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001059 self.style.configure('frame_color_set.TFrame', background=color)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001060
1061 def paint_theme_sample(self):
1062 """Apply the theme colors to each element tag in the sample text.
1063
1064 Instance attributes accessed:
1065 theme_elements
1066 theme_source
1067 builtin_name
1068 custom_name
1069
1070 Attributes updated:
1071 highlight_sample: Set the tag elements to the theme.
1072
1073 Methods:
1074 set_color_sample
1075
1076 Called from:
1077 var_changed_builtin_name
1078 var_changed_custom_name
1079 load_theme_cfg
1080 """
1081 if self.theme_source.get(): # Default theme
1082 theme = self.builtin_name.get()
1083 else: # User theme
1084 theme = self.custom_name.get()
1085 for element_title in self.theme_elements:
1086 element = self.theme_elements[element_title][0]
1087 colors = idleConf.GetHighlight(theme, element)
1088 if element == 'cursor': # Cursor sample needs special painting.
1089 colors['background'] = idleConf.GetHighlight(
Terry Jan Reedyc1419572019-03-22 18:23:41 -04001090 theme, 'normal')['background']
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001091 # Handle any unsaved changes to this theme.
1092 if theme in changes['highlight']:
1093 theme_dict = changes['highlight'][theme]
1094 if element + '-foreground' in theme_dict:
1095 colors['foreground'] = theme_dict[element + '-foreground']
1096 if element + '-background' in theme_dict:
1097 colors['background'] = theme_dict[element + '-background']
1098 self.highlight_sample.tag_config(element, **colors)
1099 self.set_color_sample()
1100
1101 def save_new(self, theme_name, theme):
1102 """Save a newly created theme to idleConf.
1103
1104 theme_name - string, the name of the new theme
1105 theme - dictionary containing the new theme
1106 """
Cheryl Sabelladd023ad2020-01-27 17:15:56 -05001107 idleConf.userCfg['highlight'].AddSection(theme_name)
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001108 for element in theme:
1109 value = theme[element]
1110 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
1111
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001112 def askyesno(self, *args, **kwargs):
1113 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001114 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001115
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001116 def delete_custom(self):
1117 """Handle event to delete custom theme.
1118
1119 The current theme is deactivated and the default theme is
1120 activated. The custom theme is permanently removed from
1121 the config file.
1122
1123 Attributes accessed:
1124 custom_name
1125
1126 Attributes updated:
1127 custom_theme_on
1128 customlist
1129 theme_source
1130 builtin_name
1131
1132 Methods:
1133 deactivate_current_config
1134 save_all_changed_extensions
1135 activate_config_changes
1136 set_theme_type
1137 """
1138 theme_name = self.custom_name.get()
1139 delmsg = 'Are you sure you wish to delete the theme %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001140 if not self.askyesno(
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001141 'Delete Theme', delmsg % theme_name, parent=self):
1142 return
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001143 self.cd.deactivate_current_config()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001144 # Remove theme from changes, config, and file.
1145 changes.delete_section('highlight', theme_name)
1146 # Reload user theme list.
1147 item_list = idleConf.GetSectionList('user', 'highlight')
1148 item_list.sort()
1149 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001150 self.custom_theme_on.state(('disabled',))
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001151 self.customlist.SetMenu(item_list, '- no custom themes -')
1152 else:
1153 self.customlist.SetMenu(item_list, item_list[0])
1154 # Revert to default theme.
1155 self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
1156 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
1157 # User can't back out of these changes, they must be applied now.
1158 changes.save_all()
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001159 self.extpage.save_all_changed_extensions()
Cheryl Sabella8f7a7982017-08-19 22:04:40 -04001160 self.cd.activate_config_changes()
Cheryl Sabellaa32e4052017-08-18 18:34:55 -04001161 self.set_theme_type()
1162
1163
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001164class KeysPage(Frame):
1165
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001166 def __init__(self, master, extpage):
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001167 super().__init__(master)
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001168 self.extpage = extpage
Mark Rosemanc579ad12020-10-24 16:45:00 -07001169 self.cd = master.winfo_toplevel()
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001170 self.create_page_keys()
1171 self.load_key_cfg()
1172
1173 def create_page_keys(self):
1174 """Return frame of widgets for Keys tab.
1175
1176 Enable users to provisionally change both individual and sets of
1177 keybindings (shortcut keys). Except for features implemented as
1178 extensions, keybindings are stored in complete sets called
1179 keysets. Built-in keysets in idlelib/config-keys.def are fixed
1180 as far as the dialog is concerned. Any keyset can be used as the
1181 base for a new custom keyset, stored in .idlerc/config-keys.cfg.
1182
1183 Function load_key_cfg() initializes tk variables and keyset
1184 lists and calls load_keys_list for the current keyset.
1185 Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
1186 keyset_source, which controls if the current set of keybindings
1187 are from a builtin or custom keyset. DynOptionMenus builtinlist
1188 and customlist contain lists of the builtin and custom keysets,
1189 respectively, and the current item from each list is stored in
1190 vars builtin_name and custom_name.
1191
1192 Button delete_custom_keys invokes delete_custom_keys() to delete
1193 a custom keyset from idleConf.userCfg['keys'] and changes. Button
1194 save_custom_keys invokes save_as_new_key_set() which calls
1195 get_new_keys_name() and create_new_key_set() to save a custom keyset
1196 and its keybindings to idleConf.userCfg['keys'].
1197
1198 Listbox bindingslist contains all of the keybindings for the
1199 selected keyset. The keybindings are loaded in load_keys_list()
1200 and are pairs of (event, [keys]) where keys can be a list
1201 of one or more key combinations to bind to the same event.
1202 Mouse button 1 click invokes on_bindingslist_select(), which
1203 allows button_new_keys to be clicked.
1204
1205 So, an item is selected in listbindings, which activates
1206 button_new_keys, and clicking button_new_keys calls function
1207 get_new_keys(). Function get_new_keys() gets the key mappings from the
1208 current keyset for the binding event item that was selected. The
1209 function then displays another dialog, GetKeysDialog, with the
Cheryl Sabella82aff622017-08-17 20:39:00 -04001210 selected binding event and current keys and allows new key sequences
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001211 to be entered for that binding event. If the keys aren't
1212 changed, nothing happens. If the keys are changed and the keyset
1213 is a builtin, function get_new_keys_name() will be called
1214 for input of a custom keyset name. If no name is given, then the
1215 change to the keybinding will abort and no updates will be made. If
1216 a custom name is entered in the prompt or if the current keyset was
1217 already custom (and thus didn't require a prompt), then
1218 idleConf.userCfg['keys'] is updated in function create_new_key_set()
1219 with the change to the event binding. The item listing in bindingslist
1220 is updated with the new keys. Var keybinding is also set which invokes
1221 the callback function, var_changed_keybinding, to add the change to
1222 the 'keys' or 'extensions' changes tracker based on the binding type.
1223
1224 Tk Variables:
1225 keybinding: Action/key bindings.
1226
1227 Methods:
1228 load_keys_list: Reload active set.
1229 create_new_key_set: Combine active keyset and changes.
1230 set_keys_type: Command for keyset_source.
1231 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1232 deactivate_current_config: Remove keys bindings in editors.
1233
1234 Widgets for KeysPage(frame): (*) widgets bound to self
1235 frame_key_sets: LabelFrame
1236 frames[0]: Frame
1237 (*)builtin_keyset_on: Radiobutton - var keyset_source
1238 (*)custom_keyset_on: Radiobutton - var keyset_source
1239 (*)builtinlist: DynOptionMenu - var builtin_name,
1240 func keybinding_selected
1241 (*)customlist: DynOptionMenu - var custom_name,
1242 func keybinding_selected
1243 (*)keys_message: Label
1244 frames[1]: Frame
1245 (*)button_delete_custom_keys: Button - delete_custom_keys
1246 (*)button_save_custom_keys: Button - save_as_new_key_set
1247 frame_custom: LabelFrame
1248 frame_target: Frame
1249 target_title: Label
1250 scroll_target_y: Scrollbar
1251 scroll_target_x: Scrollbar
1252 (*)bindingslist: ListBox - on_bindingslist_select
1253 (*)button_new_keys: Button - get_new_keys & ..._name
1254 """
1255 self.builtin_name = tracers.add(
1256 StringVar(self), self.var_changed_builtin_name)
1257 self.custom_name = tracers.add(
1258 StringVar(self), self.var_changed_custom_name)
1259 self.keyset_source = tracers.add(
1260 BooleanVar(self), self.var_changed_keyset_source)
1261 self.keybinding = tracers.add(
1262 StringVar(self), self.var_changed_keybinding)
1263
1264 # Create widgets:
1265 # body and section frames.
1266 frame_custom = LabelFrame(
1267 self, borderwidth=2, relief=GROOVE,
1268 text=' Custom Key Bindings ')
1269 frame_key_sets = LabelFrame(
1270 self, borderwidth=2, relief=GROOVE, text=' Key Set ')
1271 # frame_custom.
1272 frame_target = Frame(frame_custom)
1273 target_title = Label(frame_target, text='Action - Key(s)')
1274 scroll_target_y = Scrollbar(frame_target)
1275 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1276 self.bindingslist = Listbox(
1277 frame_target, takefocus=FALSE, exportselection=FALSE)
1278 self.bindingslist.bind('<ButtonRelease-1>',
1279 self.on_bindingslist_select)
1280 scroll_target_y['command'] = self.bindingslist.yview
1281 scroll_target_x['command'] = self.bindingslist.xview
1282 self.bindingslist['yscrollcommand'] = scroll_target_y.set
1283 self.bindingslist['xscrollcommand'] = scroll_target_x.set
1284 self.button_new_keys = Button(
1285 frame_custom, text='Get New Keys for Selection',
Terry Jan Reedye8f7c782017-11-28 21:52:32 -05001286 command=self.get_new_keys, state='disabled')
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001287 # frame_key_sets.
Cheryl Sabella7028e592017-08-26 14:26:02 -04001288 frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001289 for i in range(2)]
1290 self.builtin_keyset_on = Radiobutton(
1291 frames[0], variable=self.keyset_source, value=1,
1292 command=self.set_keys_type, text='Use a Built-in Key Set')
1293 self.custom_keyset_on = Radiobutton(
1294 frames[0], variable=self.keyset_source, value=0,
1295 command=self.set_keys_type, text='Use a Custom Key Set')
1296 self.builtinlist = DynOptionMenu(
1297 frames[0], self.builtin_name, None, command=None)
1298 self.customlist = DynOptionMenu(
1299 frames[0], self.custom_name, None, command=None)
1300 self.button_delete_custom_keys = Button(
1301 frames[1], text='Delete Custom Key Set',
1302 command=self.delete_custom_keys)
1303 self.button_save_custom_keys = Button(
1304 frames[1], text='Save as New Custom Key Set',
1305 command=self.save_as_new_key_set)
Cheryl Sabella7028e592017-08-26 14:26:02 -04001306 self.keys_message = Label(frames[0], borderwidth=2)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001307
1308 # Pack widgets:
1309 # body.
1310 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1311 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1312 # frame_custom.
1313 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1314 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
1315 # frame_target.
1316 frame_target.columnconfigure(0, weight=1)
1317 frame_target.rowconfigure(1, weight=1)
1318 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1319 self.bindingslist.grid(row=1, column=0, sticky=NSEW)
1320 scroll_target_y.grid(row=1, column=1, sticky=NS)
1321 scroll_target_x.grid(row=2, column=0, sticky=EW)
1322 # frame_key_sets.
1323 self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
1324 self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
1325 self.builtinlist.grid(row=0, column=1, sticky=NSEW)
1326 self.customlist.grid(row=1, column=1, sticky=NSEW)
1327 self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
1328 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1329 self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1330 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1331 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
1332
1333 def load_key_cfg(self):
1334 "Load current configuration settings for the keybinding options."
1335 # Set current keys type radiobutton.
1336 self.keyset_source.set(idleConf.GetOption(
1337 'main', 'Keys', 'default', type='bool', default=1))
1338 # Set current keys.
1339 current_option = idleConf.CurrentKeys()
1340 # Load available keyset option menus.
1341 if self.keyset_source.get(): # Default theme selected.
1342 item_list = idleConf.GetSectionList('default', 'keys')
1343 item_list.sort()
1344 self.builtinlist.SetMenu(item_list, current_option)
1345 item_list = idleConf.GetSectionList('user', 'keys')
1346 item_list.sort()
1347 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001348 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001349 self.custom_name.set('- no custom keys -')
1350 else:
1351 self.customlist.SetMenu(item_list, item_list[0])
1352 else: # User key set selected.
1353 item_list = idleConf.GetSectionList('user', 'keys')
1354 item_list.sort()
1355 self.customlist.SetMenu(item_list, current_option)
1356 item_list = idleConf.GetSectionList('default', 'keys')
1357 item_list.sort()
1358 self.builtinlist.SetMenu(item_list, idleConf.default_keys())
1359 self.set_keys_type()
1360 # Load keyset element list.
1361 keyset_name = idleConf.CurrentKeys()
1362 self.load_keys_list(keyset_name)
1363
1364 def var_changed_builtin_name(self, *params):
1365 "Process selection of builtin key set."
1366 old_keys = (
1367 'IDLE Classic Windows',
1368 'IDLE Classic Unix',
1369 'IDLE Classic Mac',
1370 'IDLE Classic OSX',
1371 )
1372 value = self.builtin_name.get()
1373 if value not in old_keys:
1374 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1375 changes.add_option('main', 'Keys', 'name', old_keys[0])
1376 changes.add_option('main', 'Keys', 'name2', value)
1377 self.keys_message['text'] = 'New key set, see Help'
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001378 else:
1379 changes.add_option('main', 'Keys', 'name', value)
1380 changes.add_option('main', 'Keys', 'name2', '')
1381 self.keys_message['text'] = ''
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001382 self.load_keys_list(value)
1383
1384 def var_changed_custom_name(self, *params):
1385 "Process selection of custom key set."
1386 value = self.custom_name.get()
1387 if value != '- no custom keys -':
1388 changes.add_option('main', 'Keys', 'name', value)
1389 self.load_keys_list(value)
1390
1391 def var_changed_keyset_source(self, *params):
1392 "Process toggle between builtin key set and custom key set."
1393 value = self.keyset_source.get()
1394 changes.add_option('main', 'Keys', 'default', value)
1395 if value:
1396 self.var_changed_builtin_name()
1397 else:
1398 self.var_changed_custom_name()
1399
1400 def var_changed_keybinding(self, *params):
1401 "Store change to a keybinding."
1402 value = self.keybinding.get()
1403 key_set = self.custom_name.get()
1404 event = self.bindingslist.get(ANCHOR).split()[0]
1405 if idleConf.IsCoreBinding(event):
1406 changes.add_option('keys', key_set, event, value)
1407 else: # Event is an extension binding.
1408 ext_name = idleConf.GetExtnNameForEvent(event)
1409 ext_keybind_section = ext_name + '_cfgBindings'
1410 changes.add_option('extensions', ext_keybind_section, event, value)
1411
1412 def set_keys_type(self):
1413 "Set available screen options based on builtin or custom key set."
1414 if self.keyset_source.get():
Cheryl Sabella7028e592017-08-26 14:26:02 -04001415 self.builtinlist['state'] = 'normal'
1416 self.customlist['state'] = 'disabled'
1417 self.button_delete_custom_keys.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001418 else:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001419 self.builtinlist['state'] = 'disabled'
1420 self.custom_keyset_on.state(('!disabled',))
1421 self.customlist['state'] = 'normal'
1422 self.button_delete_custom_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001423
1424 def get_new_keys(self):
1425 """Handle event to change key binding for selected line.
1426
1427 A selection of a key/binding in the list of current
1428 bindings pops up a dialog to enter a new binding. If
1429 the current key set is builtin and a binding has
1430 changed, then a name for a custom key set needs to be
1431 entered for the change to be applied.
1432 """
1433 list_index = self.bindingslist.index(ANCHOR)
1434 binding = self.bindingslist.get(list_index)
1435 bind_name = binding.split()[0]
1436 if self.keyset_source.get():
1437 current_key_set_name = self.builtin_name.get()
1438 else:
1439 current_key_set_name = self.custom_name.get()
1440 current_bindings = idleConf.GetCurrentKeySet()
1441 if current_key_set_name in changes['keys']: # unsaved changes
1442 key_set_changes = changes['keys'][current_key_set_name]
1443 for event in key_set_changes:
1444 current_bindings[event] = key_set_changes[event].split()
1445 current_key_sequences = list(current_bindings.values())
1446 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1447 current_key_sequences).result
1448 if new_keys:
1449 if self.keyset_source.get(): # Current key set is a built-in.
1450 message = ('Your changes will be saved as a new Custom Key Set.'
1451 ' Enter a name for your new Custom Key Set below.')
1452 new_keyset = self.get_new_keys_name(message)
1453 if not new_keyset: # User cancelled custom key set creation.
1454 self.bindingslist.select_set(list_index)
1455 self.bindingslist.select_anchor(list_index)
1456 return
1457 else: # Create new custom key set based on previously active key set.
1458 self.create_new_key_set(new_keyset)
1459 self.bindingslist.delete(list_index)
1460 self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
1461 self.bindingslist.select_set(list_index)
1462 self.bindingslist.select_anchor(list_index)
1463 self.keybinding.set(new_keys)
1464 else:
1465 self.bindingslist.select_set(list_index)
1466 self.bindingslist.select_anchor(list_index)
1467
1468 def get_new_keys_name(self, message):
1469 "Return new key set name from query popup."
1470 used_names = (idleConf.GetSectionList('user', 'keys') +
1471 idleConf.GetSectionList('default', 'keys'))
1472 new_keyset = SectionName(
1473 self, 'New Custom Key Set', message, used_names).result
1474 return new_keyset
1475
1476 def save_as_new_key_set(self):
1477 "Prompt for name of new key set and save changes using that name."
1478 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1479 if new_keys_name:
1480 self.create_new_key_set(new_keys_name)
1481
1482 def on_bindingslist_select(self, event):
1483 "Activate button to assign new keys to selected action."
Cheryl Sabella7028e592017-08-26 14:26:02 -04001484 self.button_new_keys.state(('!disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001485
1486 def create_new_key_set(self, new_key_set_name):
1487 """Create a new custom key set with the given name.
1488
1489 Copy the bindings/keys from the previously active keyset
1490 to the new keyset and activate the new custom keyset.
1491 """
1492 if self.keyset_source.get():
1493 prev_key_set_name = self.builtin_name.get()
1494 else:
1495 prev_key_set_name = self.custom_name.get()
1496 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1497 new_keys = {}
1498 for event in prev_keys: # Add key set to changed items.
1499 event_name = event[2:-2] # Trim off the angle brackets.
1500 binding = ' '.join(prev_keys[event])
1501 new_keys[event_name] = binding
1502 # Handle any unsaved changes to prev key set.
1503 if prev_key_set_name in changes['keys']:
1504 key_set_changes = changes['keys'][prev_key_set_name]
1505 for event in key_set_changes:
1506 new_keys[event] = key_set_changes[event]
1507 # Save the new key set.
1508 self.save_new_key_set(new_key_set_name, new_keys)
1509 # Change GUI over to the new key set.
1510 custom_key_list = idleConf.GetSectionList('user', 'keys')
1511 custom_key_list.sort()
1512 self.customlist.SetMenu(custom_key_list, new_key_set_name)
1513 self.keyset_source.set(0)
1514 self.set_keys_type()
1515
1516 def load_keys_list(self, keyset_name):
1517 """Reload the list of action/key binding pairs for the active key set.
1518
1519 An action/key binding can be selected to change the key binding.
1520 """
1521 reselect = False
1522 if self.bindingslist.curselection():
1523 reselect = True
1524 list_index = self.bindingslist.index(ANCHOR)
1525 keyset = idleConf.GetKeySet(keyset_name)
1526 bind_names = list(keyset.keys())
1527 bind_names.sort()
1528 self.bindingslist.delete(0, END)
1529 for bind_name in bind_names:
1530 key = ' '.join(keyset[bind_name])
1531 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1532 if keyset_name in changes['keys']:
1533 # Handle any unsaved changes to this key set.
1534 if bind_name in changes['keys'][keyset_name]:
1535 key = changes['keys'][keyset_name][bind_name]
1536 self.bindingslist.insert(END, bind_name+' - '+key)
1537 if reselect:
1538 self.bindingslist.see(list_index)
1539 self.bindingslist.select_set(list_index)
1540 self.bindingslist.select_anchor(list_index)
1541
1542 @staticmethod
1543 def save_new_key_set(keyset_name, keyset):
1544 """Save a newly created core key set.
1545
1546 Add keyset to idleConf.userCfg['keys'], not to disk.
1547 If the keyset doesn't exist, it is created. The
1548 binding/keys are taken from the keyset argument.
1549
1550 keyset_name - string, the name of the new key set
1551 keyset - dictionary containing the new keybindings
1552 """
Cheryl Sabelladd023ad2020-01-27 17:15:56 -05001553 idleConf.userCfg['keys'].AddSection(keyset_name)
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001554 for event in keyset:
1555 value = keyset[event]
1556 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1557
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001558 def askyesno(self, *args, **kwargs):
1559 # Make testing easier. Could change implementation.
Terry Jan Reedy0efc7c62017-09-17 20:13:25 -04001560 return messagebox.askyesno(*args, **kwargs)
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001561
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001562 def delete_custom_keys(self):
1563 """Handle event to delete a custom key set.
1564
1565 Applying the delete deactivates the current configuration and
1566 reverts to the default. The custom key set is permanently
1567 deleted from the config file.
1568 """
1569 keyset_name = self.custom_name.get()
1570 delmsg = 'Are you sure you wish to delete the key set %r ?'
Terry Jan Reedy3457f422017-08-27 16:39:41 -04001571 if not self.askyesno(
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001572 'Delete Key Set', delmsg % keyset_name, parent=self):
1573 return
1574 self.cd.deactivate_current_config()
1575 # Remove key set from changes, config, and file.
1576 changes.delete_section('keys', keyset_name)
1577 # Reload user key set list.
1578 item_list = idleConf.GetSectionList('user', 'keys')
1579 item_list.sort()
1580 if not item_list:
Cheryl Sabella7028e592017-08-26 14:26:02 -04001581 self.custom_keyset_on.state(('disabled',))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001582 self.customlist.SetMenu(item_list, '- no custom keys -')
1583 else:
1584 self.customlist.SetMenu(item_list, item_list[0])
1585 # Revert to default key set.
1586 self.keyset_source.set(idleConf.defaultCfg['main']
Tal Einat604e7b92018-09-25 15:10:14 +03001587 .Get('Keys', 'default'))
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001588 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Tal Einat604e7b92018-09-25 15:10:14 +03001589 or idleConf.default_keys())
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001590 # User can't back out of these changes, they must be applied now.
1591 changes.save_all()
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001592 self.extpage.save_all_changed_extensions()
Cheryl Sabellae36d9f52017-08-15 18:26:23 -04001593 self.cd.activate_config_changes()
1594 self.set_keys_type()
1595
1596
csabellae8eb17b2017-07-30 18:39:17 -04001597class GenPage(Frame):
1598
csabella6f446be2017-08-01 00:24:07 -04001599 def __init__(self, master):
1600 super().__init__(master)
Tal Einat1ebee372019-07-23 13:02:11 +03001601
1602 self.init_validators()
csabellae8eb17b2017-07-30 18:39:17 -04001603 self.create_page_general()
1604 self.load_general_cfg()
1605
Tal Einat1ebee372019-07-23 13:02:11 +03001606 def init_validators(self):
1607 digits_or_empty_re = re.compile(r'[0-9]*')
1608 def is_digits_or_empty(s):
1609 "Return 's is blank or contains only digits'"
1610 return digits_or_empty_re.fullmatch(s) is not None
1611 self.digits_only = (self.register(is_digits_or_empty), '%P',)
1612
csabellae8eb17b2017-07-30 18:39:17 -04001613 def create_page_general(self):
1614 """Return frame of widgets for General tab.
1615
1616 Enable users to provisionally change general options. Function
Terry Jan Reedy0acb6462019-07-30 18:14:58 -04001617 load_general_cfg initializes tk variables and helplist using
csabellae8eb17b2017-07-30 18:39:17 -04001618 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1619 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1620 set var autosave. Entry boxes win_width_int and win_height_int
1621 set var win_width and win_height. Setting var_name invokes the
1622 default callback that adds option to changes.
1623
1624 Helplist: load_general_cfg loads list user_helplist with
1625 name, position pairs and copies names to listbox helplist.
1626 Clicking a name invokes help_source selected. Clicking
1627 button_helplist_name invokes helplist_item_name, which also
1628 changes user_helplist. These functions all call
1629 set_add_delete_state. All but load call update_help_changes to
1630 rewrite changes['main']['HelpFiles'].
1631
Cheryl Sabella2f896462017-08-14 21:21:43 -04001632 Widgets for GenPage(Frame): (*) widgets bound to self
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001633 frame_window: LabelFrame
1634 frame_run: Frame
1635 startup_title: Label
1636 (*)startup_editor_on: Radiobutton - startup_edit
1637 (*)startup_shell_on: Radiobutton - startup_edit
1638 frame_win_size: Frame
1639 win_size_title: Label
1640 win_width_title: Label
1641 (*)win_width_int: Entry - win_width
1642 win_height_title: Label
1643 (*)win_height_int: Entry - win_height
Zackery Spytz9c284492019-11-13 00:13:33 -07001644 frame_cursor_blink: Frame
1645 cursor_blink_title: Label
1646 (*)cursor_blink_bool: Checkbutton - cursor_blink
Cheryl Sabella845d8642018-02-04 18:15:21 -05001647 frame_autocomplete: Frame
1648 auto_wait_title: Label
1649 (*)auto_wait_int: Entry - autocomplete_wait
1650 frame_paren1: Frame
1651 paren_style_title: Label
1652 (*)paren_style_type: OptionMenu - paren_style
1653 frame_paren2: Frame
1654 paren_time_title: Label
1655 (*)paren_flash_time: Entry - flash_delay
1656 (*)bell_on: Checkbutton - paren_bell
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001657 frame_editor: LabelFrame
1658 frame_save: Frame
1659 run_save_title: Label
1660 (*)save_ask_on: Radiobutton - autosave
1661 (*)save_auto_on: Radiobutton - autosave
Cheryl Sabella845d8642018-02-04 18:15:21 -05001662 frame_format: Frame
1663 format_width_title: Label
1664 (*)format_width_int: Entry - format_width
Tal Einat7123ea02019-07-23 15:22:11 +03001665 frame_line_numbers_default: Frame
1666 line_numbers_default_title: Label
1667 (*)line_numbers_default_bool: Checkbutton - line_numbers_default
Cheryl Sabella845d8642018-02-04 18:15:21 -05001668 frame_context: Frame
1669 context_title: Label
1670 (*)context_int: Entry - context_lines
Tal Einat604e7b92018-09-25 15:10:14 +03001671 frame_shell: LabelFrame
1672 frame_auto_squeeze_min_lines: Frame
1673 auto_squeeze_min_lines_title: Label
1674 (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
csabellae8eb17b2017-07-30 18:39:17 -04001675 """
wohlganger58fc71c2017-09-10 16:19:47 -05001676 # Integer values need StringVar because int('') raises.
csabellae8eb17b2017-07-30 18:39:17 -04001677 self.startup_edit = tracers.add(
1678 IntVar(self), ('main', 'General', 'editor-on-startup'))
csabellae8eb17b2017-07-30 18:39:17 -04001679 self.win_width = tracers.add(
1680 StringVar(self), ('main', 'EditorWindow', 'width'))
1681 self.win_height = tracers.add(
1682 StringVar(self), ('main', 'EditorWindow', 'height'))
Zackery Spytz9c284492019-11-13 00:13:33 -07001683 self.cursor_blink = tracers.add(
1684 BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
wohlganger58fc71c2017-09-10 16:19:47 -05001685 self.autocomplete_wait = tracers.add(
1686 StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
1687 self.paren_style = tracers.add(
1688 StringVar(self), ('extensions', 'ParenMatch', 'style'))
1689 self.flash_delay = tracers.add(
1690 StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
1691 self.paren_bell = tracers.add(
1692 BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
csabellae8eb17b2017-07-30 18:39:17 -04001693
Tal Einat604e7b92018-09-25 15:10:14 +03001694 self.auto_squeeze_min_lines = tracers.add(
1695 StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
1696
wohlganger58fc71c2017-09-10 16:19:47 -05001697 self.autosave = tracers.add(
1698 IntVar(self), ('main', 'General', 'autosave'))
1699 self.format_width = tracers.add(
1700 StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
Tal Einat7123ea02019-07-23 15:22:11 +03001701 self.line_numbers_default = tracers.add(
1702 BooleanVar(self),
1703 ('main', 'EditorWindow', 'line-numbers-default'))
wohlganger58fc71c2017-09-10 16:19:47 -05001704 self.context_lines = tracers.add(
Cheryl Sabella29996a12018-06-01 19:23:00 -04001705 StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
wohlganger58fc71c2017-09-10 16:19:47 -05001706
1707 # Create widgets:
csabellae8eb17b2017-07-30 18:39:17 -04001708 # Section frames.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001709 frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
1710 text=' Window Preferences')
1711 frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
1712 text=' Editor Preferences')
Tal Einat604e7b92018-09-25 15:10:14 +03001713 frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
1714 text=' Shell Preferences')
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001715 # Frame_window.
1716 frame_run = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001717 startup_title = Label(frame_run, text='At Startup')
1718 self.startup_editor_on = Radiobutton(
1719 frame_run, variable=self.startup_edit, value=1,
1720 text="Open Edit Window")
1721 self.startup_shell_on = Radiobutton(
1722 frame_run, variable=self.startup_edit, value=0,
1723 text='Open Shell Window')
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001724
wohlganger58fc71c2017-09-10 16:19:47 -05001725 frame_win_size = Frame(frame_window, borderwidth=0)
csabellae8eb17b2017-07-30 18:39:17 -04001726 win_size_title = Label(
1727 frame_win_size, text='Initial Window Size (in characters)')
1728 win_width_title = Label(frame_win_size, text='Width')
1729 self.win_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001730 frame_win_size, textvariable=self.win_width, width=3,
1731 validatecommand=self.digits_only, validate='key',
1732 )
csabellae8eb17b2017-07-30 18:39:17 -04001733 win_height_title = Label(frame_win_size, text='Height')
1734 self.win_height_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001735 frame_win_size, textvariable=self.win_height, width=3,
1736 validatecommand=self.digits_only, validate='key',
1737 )
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001738
Zackery Spytz9c284492019-11-13 00:13:33 -07001739 frame_cursor_blink = Frame(frame_window, borderwidth=0)
1740 cursor_blink_title = Label(frame_cursor_blink, text='Cursor Blink')
1741 self.cursor_blink_bool = Checkbutton(frame_cursor_blink,
1742 variable=self.cursor_blink, width=1)
1743
wohlganger58fc71c2017-09-10 16:19:47 -05001744 frame_autocomplete = Frame(frame_window, borderwidth=0,)
1745 auto_wait_title = Label(frame_autocomplete,
1746 text='Completions Popup Wait (milliseconds)')
1747 self.auto_wait_int = Entry(frame_autocomplete, width=6,
Tal Einat1ebee372019-07-23 13:02:11 +03001748 textvariable=self.autocomplete_wait,
1749 validatecommand=self.digits_only,
1750 validate='key',
1751 )
wohlganger58fc71c2017-09-10 16:19:47 -05001752
1753 frame_paren1 = Frame(frame_window, borderwidth=0)
1754 paren_style_title = Label(frame_paren1, text='Paren Match Style')
1755 self.paren_style_type = OptionMenu(
1756 frame_paren1, self.paren_style, 'expression',
1757 "opener","parens","expression")
1758 frame_paren2 = Frame(frame_window, borderwidth=0)
1759 paren_time_title = Label(
1760 frame_paren2, text='Time Match Displayed (milliseconds)\n'
1761 '(0 is until next input)')
1762 self.paren_flash_time = Entry(
1763 frame_paren2, textvariable=self.flash_delay, width=6)
1764 self.bell_on = Checkbutton(
1765 frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
1766
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001767 # Frame_editor.
1768 frame_save = Frame(frame_editor, borderwidth=0)
1769 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
1770 self.save_ask_on = Radiobutton(
1771 frame_save, variable=self.autosave, value=0,
1772 text="Prompt to Save")
1773 self.save_auto_on = Radiobutton(
1774 frame_save, variable=self.autosave, value=1,
1775 text='No Prompt')
1776
wohlganger58fc71c2017-09-10 16:19:47 -05001777 frame_format = Frame(frame_editor, borderwidth=0)
1778 format_width_title = Label(frame_format,
1779 text='Format Paragraph Max Width')
1780 self.format_width_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001781 frame_format, textvariable=self.format_width, width=4,
1782 validatecommand=self.digits_only, validate='key',
1783 )
wohlganger58fc71c2017-09-10 16:19:47 -05001784
Tal Einat7123ea02019-07-23 15:22:11 +03001785 frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
1786 line_numbers_default_title = Label(
1787 frame_line_numbers_default, text='Show line numbers in new windows')
1788 self.line_numbers_default_bool = Checkbutton(
1789 frame_line_numbers_default,
1790 variable=self.line_numbers_default,
1791 width=1)
1792
wohlganger58fc71c2017-09-10 16:19:47 -05001793 frame_context = Frame(frame_editor, borderwidth=0)
Cheryl Sabella29996a12018-06-01 19:23:00 -04001794 context_title = Label(frame_context, text='Max Context Lines :')
wohlganger58fc71c2017-09-10 16:19:47 -05001795 self.context_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001796 frame_context, textvariable=self.context_lines, width=3,
1797 validatecommand=self.digits_only, validate='key',
1798 )
wohlganger58fc71c2017-09-10 16:19:47 -05001799
Tal Einat604e7b92018-09-25 15:10:14 +03001800 # Frame_shell.
1801 frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
1802 auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
1803 text='Auto-Squeeze Min. Lines:')
1804 self.auto_squeeze_min_lines_int = Entry(
Tal Einat1ebee372019-07-23 13:02:11 +03001805 frame_auto_squeeze_min_lines, width=4,
1806 textvariable=self.auto_squeeze_min_lines,
1807 validatecommand=self.digits_only, validate='key',
1808 )
wohlganger58fc71c2017-09-10 16:19:47 -05001809
csabellae8eb17b2017-07-30 18:39:17 -04001810 # Pack widgets:
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001811 # Body.
1812 frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1813 frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Tal Einat604e7b92018-09-25 15:10:14 +03001814 frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
csabellae8eb17b2017-07-30 18:39:17 -04001815 # frame_run.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001816 frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04001817 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1818 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1819 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
csabellae8eb17b2017-07-30 18:39:17 -04001820 # frame_win_size.
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001821 frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
csabellae8eb17b2017-07-30 18:39:17 -04001822 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1823 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
1824 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
1825 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
1826 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Zackery Spytz9c284492019-11-13 00:13:33 -07001827 # frame_cursor_blink.
1828 frame_cursor_blink.pack(side=TOP, padx=5, pady=0, fill=X)
1829 cursor_blink_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1830 self.cursor_blink_bool.pack(side=LEFT, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05001831 # frame_autocomplete.
1832 frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
1833 auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1834 self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
1835 # frame_paren.
1836 frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
1837 paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1838 self.paren_style_type.pack(side=TOP, padx=10, pady=5)
1839 frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
1840 paren_time_title.pack(side=LEFT, anchor=W, padx=5)
1841 self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
1842 self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
1843
Terry Jan Reedy390eadd2017-08-30 00:59:11 -04001844 # frame_save.
1845 frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
1846 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1847 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1848 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05001849 # frame_format.
1850 frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
1851 format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1852 self.format_width_int.pack(side=TOP, padx=10, pady=5)
Tal Einat7123ea02019-07-23 15:22:11 +03001853 # frame_line_numbers_default.
1854 frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
1855 line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1856 self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
wohlganger58fc71c2017-09-10 16:19:47 -05001857 # frame_context.
1858 frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
1859 context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1860 self.context_int.pack(side=TOP, padx=5, pady=5)
1861
Tal Einat604e7b92018-09-25 15:10:14 +03001862 # frame_auto_squeeze_min_lines
1863 frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
1864 auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1865 self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
1866
csabellae8eb17b2017-07-30 18:39:17 -04001867 def load_general_cfg(self):
1868 "Load current configuration settings for the general options."
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07001869 self.load_windows_cfg()
1870 self.load_shelled_cfg()
1871
1872 def load_windows_cfg(self):
wohlganger58fc71c2017-09-10 16:19:47 -05001873 # Set variables for all windows.
csabellae8eb17b2017-07-30 18:39:17 -04001874 self.startup_edit.set(idleConf.GetOption(
wohlganger58fc71c2017-09-10 16:19:47 -05001875 'main', 'General', 'editor-on-startup', type='bool'))
csabellae8eb17b2017-07-30 18:39:17 -04001876 self.win_width.set(idleConf.GetOption(
1877 'main', 'EditorWindow', 'width', type='int'))
1878 self.win_height.set(idleConf.GetOption(
1879 'main', 'EditorWindow', 'height', type='int'))
Zackery Spytz9c284492019-11-13 00:13:33 -07001880 self.cursor_blink.set(idleConf.GetOption(
1881 'main', 'EditorWindow', 'cursor-blink', type='bool'))
wohlganger58fc71c2017-09-10 16:19:47 -05001882 self.autocomplete_wait.set(idleConf.GetOption(
1883 'extensions', 'AutoComplete', 'popupwait', type='int'))
1884 self.paren_style.set(idleConf.GetOption(
1885 'extensions', 'ParenMatch', 'style'))
1886 self.flash_delay.set(idleConf.GetOption(
1887 'extensions', 'ParenMatch', 'flash-delay', type='int'))
1888 self.paren_bell.set(idleConf.GetOption(
1889 'extensions', 'ParenMatch', 'bell'))
1890
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07001891 def load_shelled_cfg(self):
wohlganger58fc71c2017-09-10 16:19:47 -05001892 # Set variables for editor windows.
1893 self.autosave.set(idleConf.GetOption(
1894 'main', 'General', 'autosave', default=0, type='bool'))
1895 self.format_width.set(idleConf.GetOption(
1896 'extensions', 'FormatParagraph', 'max-width', type='int'))
Tal Einat7123ea02019-07-23 15:22:11 +03001897 self.line_numbers_default.set(idleConf.GetOption(
1898 'main', 'EditorWindow', 'line-numbers-default', type='bool'))
wohlganger58fc71c2017-09-10 16:19:47 -05001899 self.context_lines.set(idleConf.GetOption(
Cheryl Sabella29996a12018-06-01 19:23:00 -04001900 'extensions', 'CodeContext', 'maxlines', type='int'))
wohlganger58fc71c2017-09-10 16:19:47 -05001901
Tal Einat604e7b92018-09-25 15:10:14 +03001902 # Set variables for shell windows.
1903 self.auto_squeeze_min_lines.set(idleConf.GetOption(
1904 'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
1905
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07001906
Miss Islington (bot)33a7a242021-06-08 19:11:26 -07001907class ExtPage(Frame):
1908 def __init__(self, master):
1909 super().__init__(master)
1910 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1911 self.ext_userCfg = idleConf.userCfg['extensions']
1912 self.is_int = self.register(is_int)
1913 self.load_extensions()
1914 self.create_page_extensions() # Requires extension names.
1915
1916 def create_page_extensions(self):
1917 """Configure IDLE feature extensions and help menu extensions.
1918
1919 List the feature extensions and a configuration box for the
1920 selected extension. Help menu extensions are in a HelpFrame.
1921
1922 This code reads the current configuration using idleConf,
1923 supplies a GUI interface to change the configuration values,
1924 and saves the changes using idleConf.
1925
1926 Some changes may require restarting IDLE. This depends on each
1927 extension's implementation.
1928
1929 All values are treated as text, and it is up to the user to
1930 supply reasonable values. The only exception to this are the
1931 'enable*' options, which are boolean, and can be toggled with a
1932 True/False button.
1933
1934 Methods:
1935 extension_selected: Handle selection from list.
1936 create_extension_frame: Hold widgets for one extension.
1937 set_extension_value: Set in userCfg['extensions'].
1938 save_all_changed_extensions: Call extension page Save().
1939 """
1940 self.extension_names = StringVar(self)
1941
1942 frame_ext = LabelFrame(self, borderwidth=2, relief=GROOVE,
1943 text=' Feature Extensions ')
1944 self.frame_help = HelpFrame(self, borderwidth=2, relief=GROOVE,
1945 text=' Help Menu Extensions ')
1946
1947 frame_ext.rowconfigure(0, weight=1)
1948 frame_ext.columnconfigure(2, weight=1)
1949 self.extension_list = Listbox(frame_ext, listvariable=self.extension_names,
1950 selectmode='browse')
1951 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1952 scroll = Scrollbar(frame_ext, command=self.extension_list.yview)
1953 self.extension_list.yscrollcommand=scroll.set
1954 self.details_frame = LabelFrame(frame_ext, width=250, height=250)
1955 self.extension_list.grid(column=0, row=0, sticky='nws')
1956 scroll.grid(column=1, row=0, sticky='ns')
1957 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1958 frame_ext.configure(padding=10)
1959 self.config_frame = {}
1960 self.current_extension = None
1961
1962 self.outerframe = self # TEMPORARY
1963 self.tabbed_page_set = self.extension_list # TEMPORARY
1964
1965 # Create the frame holding controls for each extension.
1966 ext_names = ''
1967 for ext_name in sorted(self.extensions):
1968 self.create_extension_frame(ext_name)
1969 ext_names = ext_names + '{' + ext_name + '} '
1970 self.extension_names.set(ext_names)
1971 self.extension_list.selection_set(0)
1972 self.extension_selected(None)
1973
1974
1975 frame_ext.grid(row=0, column=0, sticky='nsew')
1976 Label(self).grid(row=1, column=0) # Spacer. Replace with config?
1977 self.frame_help.grid(row=2, column=0, sticky='sew')
1978
1979 def load_extensions(self):
1980 "Fill self.extensions with data from the default and user configs."
1981 self.extensions = {}
1982 for ext_name in idleConf.GetExtensions(active_only=False):
1983 # Former built-in extensions are already filtered out.
1984 self.extensions[ext_name] = []
1985
1986 for ext_name in self.extensions:
1987 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1988
1989 # Bring 'enable' options to the beginning of the list.
1990 enables = [opt_name for opt_name in opt_list
1991 if opt_name.startswith('enable')]
1992 for opt_name in enables:
1993 opt_list.remove(opt_name)
1994 opt_list = enables + opt_list
1995
1996 for opt_name in opt_list:
1997 def_str = self.ext_defaultCfg.Get(
1998 ext_name, opt_name, raw=True)
1999 try:
2000 def_obj = {'True':True, 'False':False}[def_str]
2001 opt_type = 'bool'
2002 except KeyError:
2003 try:
2004 def_obj = int(def_str)
2005 opt_type = 'int'
2006 except ValueError:
2007 def_obj = def_str
2008 opt_type = None
2009 try:
2010 value = self.ext_userCfg.Get(
2011 ext_name, opt_name, type=opt_type, raw=True,
2012 default=def_obj)
2013 except ValueError: # Need this until .Get fixed.
2014 value = def_obj # Bad values overwritten by entry.
2015 var = StringVar(self)
2016 var.set(str(value))
2017
2018 self.extensions[ext_name].append({'name': opt_name,
2019 'type': opt_type,
2020 'default': def_str,
2021 'value': value,
2022 'var': var,
2023 })
2024
2025 def extension_selected(self, event):
2026 "Handle selection of an extension from the list."
2027 newsel = self.extension_list.curselection()
2028 if newsel:
2029 newsel = self.extension_list.get(newsel)
2030 if newsel is None or newsel != self.current_extension:
2031 if self.current_extension:
2032 self.details_frame.config(text='')
2033 self.config_frame[self.current_extension].grid_forget()
2034 self.current_extension = None
2035 if newsel:
2036 self.details_frame.config(text=newsel)
2037 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
2038 self.current_extension = newsel
2039
2040 def create_extension_frame(self, ext_name):
2041 """Create a frame holding the widgets to configure one extension"""
2042 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
2043 self.config_frame[ext_name] = f
2044 entry_area = f.interior
2045 # Create an entry for each configuration option.
2046 for row, opt in enumerate(self.extensions[ext_name]):
2047 # Create a row with a label and entry/checkbutton.
2048 label = Label(entry_area, text=opt['name'])
2049 label.grid(row=row, column=0, sticky=NW)
2050 var = opt['var']
2051 if opt['type'] == 'bool':
2052 Checkbutton(entry_area, variable=var,
2053 onvalue='True', offvalue='False', width=8
2054 ).grid(row=row, column=1, sticky=W, padx=7)
2055 elif opt['type'] == 'int':
2056 Entry(entry_area, textvariable=var, validate='key',
2057 validatecommand=(self.is_int, '%P'), width=10
2058 ).grid(row=row, column=1, sticky=NSEW, padx=7)
2059
2060 else: # type == 'str'
2061 # Limit size to fit non-expanding space with larger font.
2062 Entry(entry_area, textvariable=var, width=15
2063 ).grid(row=row, column=1, sticky=NSEW, padx=7)
2064 return
2065
2066 def set_extension_value(self, section, opt):
2067 """Return True if the configuration was added or changed.
2068
2069 If the value is the same as the default, then remove it
2070 from user config file.
2071 """
2072 name = opt['name']
2073 default = opt['default']
2074 value = opt['var'].get().strip() or default
2075 opt['var'].set(value)
2076 # if self.defaultCfg.has_section(section):
2077 # Currently, always true; if not, indent to return.
2078 if (value == default):
2079 return self.ext_userCfg.RemoveOption(section, name)
2080 # Set the option.
2081 return self.ext_userCfg.SetOption(section, name, value)
2082
2083 def save_all_changed_extensions(self):
2084 """Save configuration changes to the user config file.
2085
2086 Attributes accessed:
2087 extensions
2088
2089 Methods:
2090 set_extension_value
2091 """
2092 has_changes = False
2093 for ext_name in self.extensions:
2094 options = self.extensions[ext_name]
2095 for opt in options:
2096 if self.set_extension_value(ext_name, opt):
2097 has_changes = True
2098 if has_changes:
2099 self.ext_userCfg.Save()
2100
2101
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07002102class HelpFrame(LabelFrame):
2103
2104 def __init__(self, master, **cfg):
2105 super().__init__(master, **cfg)
2106 self.create_frame_help()
2107 self.load_helplist()
2108
2109 def create_frame_help(self):
2110 """Create LabelFrame for additional help menu sources.
2111
2112 load_helplist loads list user_helplist with
2113 name, position pairs and copies names to listbox helplist.
2114 Clicking a name invokes help_source selected. Clicking
2115 button_helplist_name invokes helplist_item_name, which also
2116 changes user_helplist. These functions all call
2117 set_add_delete_state. All but load call update_help_changes to
2118 rewrite changes['main']['HelpFiles'].
2119
2120 Widgets for HelpFrame(LabelFrame): (*) widgets bound to self
2121 frame_helplist: Frame
2122 (*)helplist: ListBox
2123 scroll_helplist: Scrollbar
2124 frame_buttons: Frame
2125 (*)button_helplist_edit
2126 (*)button_helplist_add
2127 (*)button_helplist_remove
2128 """
2129 # self = frame_help in dialog (until ExtPage class).
2130 frame_helplist = Frame(self)
2131 self.helplist = Listbox(
2132 frame_helplist, height=5, takefocus=True,
2133 exportselection=FALSE)
2134 scroll_helplist = Scrollbar(frame_helplist)
2135 scroll_helplist['command'] = self.helplist.yview
2136 self.helplist['yscrollcommand'] = scroll_helplist.set
2137 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
2138
2139 frame_buttons = Frame(self)
2140 self.button_helplist_edit = Button(
2141 frame_buttons, text='Edit', state='disabled',
2142 width=8, command=self.helplist_item_edit)
2143 self.button_helplist_add = Button(
2144 frame_buttons, text='Add',
2145 width=8, command=self.helplist_item_add)
2146 self.button_helplist_remove = Button(
2147 frame_buttons, text='Remove', state='disabled',
2148 width=8, command=self.helplist_item_remove)
2149
2150 # Pack frame_help.
2151 frame_helplist.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
2152 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
2153 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
2154 frame_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
2155 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
2156 self.button_helplist_add.pack(side=TOP, anchor=W)
2157 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
csabellae8eb17b2017-07-30 18:39:17 -04002158
2159 def help_source_selected(self, event):
2160 "Handle event for selecting additional help."
2161 self.set_add_delete_state()
2162
2163 def set_add_delete_state(self):
2164 "Toggle the state for the help list buttons based on list entries."
2165 if self.helplist.size() < 1: # No entries in list.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002166 self.button_helplist_edit.state(('disabled',))
2167 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002168 else: # Some entries.
2169 if self.helplist.curselection(): # There currently is a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002170 self.button_helplist_edit.state(('!disabled',))
2171 self.button_helplist_remove.state(('!disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002172 else: # There currently is not a selection.
Cheryl Sabella7028e592017-08-26 14:26:02 -04002173 self.button_helplist_edit.state(('disabled',))
2174 self.button_helplist_remove.state(('disabled',))
csabellae8eb17b2017-07-30 18:39:17 -04002175
2176 def helplist_item_add(self):
2177 """Handle add button for the help list.
2178
2179 Query for name and location of new help sources and add
2180 them to the list.
2181 """
2182 help_source = HelpSource(self, 'New Help Source').result
2183 if help_source:
2184 self.user_helplist.append(help_source)
2185 self.helplist.insert(END, help_source[0])
2186 self.update_help_changes()
2187
2188 def helplist_item_edit(self):
2189 """Handle edit button for the help list.
2190
2191 Query with existing help source information and update
2192 config if the values are changed.
2193 """
2194 item_index = self.helplist.index(ANCHOR)
2195 help_source = self.user_helplist[item_index]
2196 new_help_source = HelpSource(
2197 self, 'Edit Help Source',
2198 menuitem=help_source[0],
2199 filepath=help_source[1],
2200 ).result
2201 if new_help_source and new_help_source != help_source:
2202 self.user_helplist[item_index] = new_help_source
2203 self.helplist.delete(item_index)
2204 self.helplist.insert(item_index, new_help_source[0])
2205 self.update_help_changes()
2206 self.set_add_delete_state() # Selected will be un-selected
2207
2208 def helplist_item_remove(self):
2209 """Handle remove button for the help list.
2210
2211 Delete the help list item from config.
2212 """
2213 item_index = self.helplist.index(ANCHOR)
2214 del(self.user_helplist[item_index])
2215 self.helplist.delete(item_index)
2216 self.update_help_changes()
2217 self.set_add_delete_state()
2218
2219 def update_help_changes(self):
2220 "Clear and rebuild the HelpFiles section in changes"
2221 changes['main']['HelpFiles'] = {}
2222 for num in range(1, len(self.user_helplist) + 1):
2223 changes.add_option(
2224 'main', 'HelpFiles', str(num),
2225 ';'.join(self.user_helplist[num-1][:2]))
2226
Miss Islington (bot)2cfe0e72021-06-08 13:01:23 -07002227 def load_helplist(self):
2228 # Set additional help sources.
2229 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
2230 self.helplist.delete(0, 'end')
2231 for help_item in self.user_helplist:
2232 self.helplist.insert(END, help_item[0])
2233 self.set_add_delete_state()
2234
csabellae8eb17b2017-07-30 18:39:17 -04002235
csabella45bf7232017-07-26 19:09:58 -04002236class VarTrace:
2237 """Maintain Tk variables trace state."""
2238
2239 def __init__(self):
2240 """Store Tk variables and callbacks.
2241
2242 untraced: List of tuples (var, callback)
2243 that do not have the callback attached
2244 to the Tk var.
2245 traced: List of tuples (var, callback) where
2246 that callback has been attached to the var.
2247 """
2248 self.untraced = []
2249 self.traced = []
2250
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002251 def clear(self):
2252 "Clear lists (for tests)."
Terry Jan Reedy733d0f62017-08-07 14:22:44 -04002253 # Call after all tests in a module to avoid memory leaks.
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04002254 self.untraced.clear()
2255 self.traced.clear()
2256
csabella45bf7232017-07-26 19:09:58 -04002257 def add(self, var, callback):
2258 """Add (var, callback) tuple to untraced list.
2259
2260 Args:
2261 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04002262 callback: Either function name to be used as a callback
2263 or a tuple with IdleConf config-type, section, and
2264 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04002265
2266 Return:
2267 Tk variable instance.
2268 """
2269 if isinstance(callback, tuple):
2270 callback = self.make_callback(var, callback)
2271 self.untraced.append((var, callback))
2272 return var
2273
2274 @staticmethod
2275 def make_callback(var, config):
2276 "Return default callback function to add values to changes instance."
2277 def default_callback(*params):
2278 "Add config values to changes instance."
2279 changes.add_option(*config, var.get())
2280 return default_callback
2281
2282 def attach(self):
2283 "Attach callback to all vars that are not traced."
2284 while self.untraced:
2285 var, callback = self.untraced.pop()
2286 var.trace_add('write', callback)
2287 self.traced.append((var, callback))
2288
2289 def detach(self):
2290 "Remove callback from traced vars."
2291 while self.traced:
2292 var, callback = self.traced.pop()
2293 var.trace_remove('write', var.trace_info()[0][1])
2294 self.untraced.append((var, callback))
2295
2296
csabella5b591542017-07-28 14:40:59 -04002297tracers = VarTrace()
2298
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002299help_common = '''\
2300When you click either the Apply or Ok buttons, settings in this
2301dialog that are different from IDLE's default are saved in
2302a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002303these changes apply to all versions of IDLE installed on this
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002304machine. [Cancel] only cancels changes made since the last save.
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002305'''
2306help_pages = {
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002307 'Fonts/Tabs':'''
2308Font sample: This shows what a selection of Basic Multilingual Plane
2309unicode characters look like for the current font selection. If the
2310selected font does not define a character, Tk attempts to find another
2311font that does. Substitute glyphs depend on what is available on a
2312particular system and will not necessarily have the same size as the
2313font selected. Line contains 20 characters up to Devanagari, 14 for
2314Tamil, and 10 for East Asia.
2315
2316Hebrew and Arabic letters should display right to left, starting with
2317alef, \u05d0 and \u0627. Arabic digits display left to right. The
2318Devanagari and Tamil lines start with digits. The East Asian lines
2319are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
2320Hiragana and Katakana.
Serhiy Storchakaed6554c2017-10-28 03:22:44 +03002321
2322You can edit the font sample. Changes remain until IDLE is closed.
Terry Jan Reedye2e42272017-10-17 18:56:16 -04002323''',
Cheryl Sabella3866d9b2017-09-10 22:41:10 -04002324 'Highlights': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002325Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05002326The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002327be used with older IDLE releases if it is saved as a custom
2328theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04002329''',
2330 'Keys': '''
2331Keys:
2332The IDLE Modern Unix key set is new in June 2016. It can only
2333be used with older IDLE releases if it is saved as a custom
2334key set, with a different name.
2335''',
wohlganger58fc71c2017-09-10 16:19:47 -05002336 'General': '''
2337General:
wohlgangerfae2c352017-06-27 21:36:23 -05002338
penguindustin96466302019-05-06 14:57:17 -04002339AutoComplete: Popupwait is milliseconds to wait after key char, without
wohlgangerfae2c352017-06-27 21:36:23 -05002340cursor movement, before popping up completion box. Key char is '.' after
2341identifier or a '/' (or '\\' on Windows) within a string.
2342
2343FormatParagraph: Max-width is max chars in lines after re-formatting.
2344Use with paragraphs in both strings and comment blocks.
2345
2346ParenMatch: Style indicates what is highlighted when closer is entered:
2347'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
2348'expression' (default) - also everything in between. Flash-delay is how
2349long to highlight if cursor is not moved (0 means forever).
Cheryl Sabella29996a12018-06-01 19:23:00 -04002350
2351CodeContext: Maxlines is the maximum number of code context lines to
2352display when Code Context is turned on for an editor window.
Tal Einat604e7b92018-09-25 15:10:14 +03002353
2354Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
2355of output to automatically "squeeze".
Cheryl Sabellae40e2a22021-01-05 02:26:43 -05002356''',
2357 'Extensions': '''
2358ZzDummy: This extension is provided as an example for how to create and
2359use an extension. Enable indicates whether the extension is active or
2360not; likewise enable_editor and enable_shell indicate which windows it
2361will be active on. For this extension, z-text is the text that will be
2362inserted at or removed from the beginning of the lines of selected text,
2363or the current line if no selection.
2364''',
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04002365}
2366
Steven M. Gavac11ccf32001-09-24 09:43:17 +00002367
Terry Jan Reedy93f35422015-10-13 22:03:51 -04002368def is_int(s):
2369 "Return 's is blank or represents an int'"
2370 if not s:
2371 return True
2372 try:
2373 int(s)
2374 return True
2375 except ValueError:
2376 return False
2377
2378
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002379class VerticalScrolledFrame(Frame):
2380 """A pure Tkinter vertically scrollable frame.
2381
2382 * Use the 'interior' attribute to place widgets inside the scrollable frame
2383 * Construct and pack/place/grid normally
2384 * This frame only allows vertical scrolling
2385 """
2386 def __init__(self, parent, *args, **kw):
2387 Frame.__init__(self, parent, *args, **kw)
2388
csabella7eb58832017-07-04 21:30:58 -04002389 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002390 vscrollbar = Scrollbar(self, orient=VERTICAL)
2391 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
Cheryl Sabella7028e592017-08-26 14:26:02 -04002392 canvas = Canvas(self, borderwidth=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04002393 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002394 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
2395 vscrollbar.config(command=canvas.yview)
2396
csabella7eb58832017-07-04 21:30:58 -04002397 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002398 canvas.xview_moveto(0)
2399 canvas.yview_moveto(0)
2400
csabella7eb58832017-07-04 21:30:58 -04002401 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002402 self.interior = interior = Frame(canvas)
2403 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
2404
csabella7eb58832017-07-04 21:30:58 -04002405 # Track changes to the canvas and frame width and sync them,
2406 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002407 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04002408 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002409 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
2410 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002411 interior.bind('<Configure>', _configure_interior)
2412
2413 def _configure_canvas(event):
2414 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04002415 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002416 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
2417 canvas.bind('<Configure>', _configure_canvas)
2418
2419 return
2420
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002421
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00002422if __name__ == '__main__':
Terry Jan Reedy4d921582018-06-19 19:12:52 -04002423 from unittest import main
2424 main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
2425
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04002426 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04002427 run(ConfigDialog)