blob: e359ec24cd3a5bb48ca50bef4f75d6636b559987 [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"""
csabellabac7d332017-06-26 17:46:26 -040012from tkinter import (Toplevel, Frame, LabelFrame, Listbox, Label, Button,
13 Entry, Text, Scale, Radiobutton, Checkbutton, Canvas,
14 StringVar, BooleanVar, IntVar, TRUE, FALSE,
15 TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE, NORMAL, DISABLED,
16 NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
Louie Lubb2bae82017-07-10 06:57:18 +080017 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
Terry Jan Reedy01e35752016-06-10 18:19:21 -040018from tkinter.ttk import Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000019import tkinter.colorchooser as tkColorChooser
20import tkinter.font as tkFont
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021import tkinter.messagebox as tkMessageBox
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000022
terryjreedy349abd92017-07-07 16:00:57 -040023from idlelib.config import idleConf, ConfigChanges
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040024from idlelib.config_key import GetKeysDialog
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040025from idlelib.dynoption import DynOptionMenu
26from idlelib import macosx
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -040027from idlelib.query import SectionName, HelpSource
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040028from idlelib.tabbedpages import TabbedPageSet
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040029from idlelib.textview import view_text
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -040030
terryjreedy349abd92017-07-07 16:00:57 -040031changes = ConfigChanges()
32
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000033class ConfigDialog(Toplevel):
csabella7eb58832017-07-04 21:30:58 -040034 """Config dialog for IDLE.
35 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000036
Terry Jan Reedycd567362014-10-17 01:31:35 -040037 def __init__(self, parent, title='', _htest=False, _utest=False):
csabella7eb58832017-07-04 21:30:58 -040038 """Show the tabbed dialog for user configuration.
39
csabella36329a42017-07-13 23:32:01 -040040 Args:
41 parent - parent of this dialog
42 title - string which is the title of this popup dialog
43 _htest - bool, change box location when running htest
44 _utest - bool, don't wait_window when running unittest
45
46 Note: Focus set on font page fontlist.
47
48 Methods:
49 create_widgets
50 cancel: Bound to DELETE_WINDOW protocol.
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040051 """
Steven M. Gavad721c482001-07-31 10:46:53 +000052 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040053 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040054 if _htest:
55 parent.instance_dict = {}
Louie Lu9b622fb2017-07-14 08:35:48 +080056 if not _utest:
57 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000058
Steven M. Gavad721c482001-07-31 10:46:53 +000059 self.configure(borderwidth=5)
Terry Jan Reedycd567362014-10-17 01:31:35 -040060 self.title(title or 'IDLE Preferences')
csabellabac7d332017-06-26 17:46:26 -040061 x = parent.winfo_rootx() + 20
62 y = parent.winfo_rooty() + (30 if not _htest else 150)
63 self.geometry(f'+{x}+{y}')
csabella7eb58832017-07-04 21:30:58 -040064 # Each theme element key is its display name.
65 # The first value of the tuple is the sample area tag name.
66 # The second value is the display name list sort index.
csabellabac7d332017-06-26 17:46:26 -040067 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040068 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000069 self.transient(parent)
csabellabac7d332017-06-26 17:46:26 -040070 self.protocol("WM_DELETE_WINDOW", self.cancel)
Louie Lubb2bae82017-07-10 06:57:18 +080071 self.fontlist.focus_set()
csabella7eb58832017-07-04 21:30:58 -040072 # XXX Decide whether to keep or delete these key bindings.
73 # Key bindings for this dialog.
74 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
75 # self.bind('<Alt-a>', self.Apply) #apply changes, save
76 # self.bind('<F1>', self.Help) #context help
csabellabac7d332017-06-26 17:46:26 -040077 self.load_configs()
csabella7eb58832017-07-04 21:30:58 -040078 self.attach_var_callbacks() # Avoid callbacks during load_configs.
Guido van Rossum8ce8a782007-11-01 19:42:39 +000079
Terry Jan Reedycfa89502014-07-14 23:07:32 -040080 if not _utest:
Louie Lu9b622fb2017-07-14 08:35:48 +080081 self.grab_set()
Terry Jan Reedycfa89502014-07-14 23:07:32 -040082 self.wm_deiconify()
83 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000084
csabellabac7d332017-06-26 17:46:26 -040085 def create_widgets(self):
csabella36329a42017-07-13 23:32:01 -040086 """Create and place widgets for tabbed dialog.
87
88 Widgets Bound to self:
89 tab_pages: TabbedPageSet
90
91 Methods:
92 create_page_font_tab
93 create_page_highlight
94 create_page_keys
95 create_page_general
96 create_page_extensions
97 create_action_buttons
98 load_configs: Load pages except for extensions.
99 attach_var_callbacks
100 remove_var_callbacks
101 activate_config_changes: Tell editors to reload.
102 """
csabellabac7d332017-06-26 17:46:26 -0400103 self.tab_pages = TabbedPageSet(self,
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400104 page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
105 'Extensions'])
csabellabac7d332017-06-26 17:46:26 -0400106 self.tab_pages.pack(side=TOP, expand=TRUE, fill=BOTH)
107 self.create_page_font_tab()
108 self.create_page_highlight()
109 self.create_page_keys()
110 self.create_page_general()
111 self.create_page_extensions()
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400112 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400113
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400114 def load_configs(self):
115 """Load configuration for each page.
116
117 Load configuration from default and user config files and populate
118 the widgets on the config dialog pages.
119
120 Methods:
121 load_font_cfg
122 load_tab_cfg
123 load_theme_cfg
124 load_key_cfg
125 load_general_cfg
126 """
127 self.load_font_cfg()
128 self.load_tab_cfg()
129 self.load_theme_cfg()
130 self.load_key_cfg()
131 self.load_general_cfg()
132 # note: extension page handled separately
133
134 def attach_var_callbacks(self):
135 "Attach callbacks to variables that can be changed."
136 self.font_size.trace_add('write', self.var_changed_font)
137 self.font_name.trace_add('write', self.var_changed_font)
138 self.font_bold.trace_add('write', self.var_changed_font)
139 self.space_num.trace_add('write', self.var_changed_space_num)
140 self.color.trace_add('write', self.var_changed_color)
141 self.builtin_theme.trace_add('write', self.var_changed_builtin_theme)
142 self.custom_theme.trace_add('write', self.var_changed_custom_theme)
143 self.is_builtin_theme.trace_add('write', self.var_changed_is_builtin_theme)
144 self.highlight_target.trace_add('write', self.var_changed_highlight_target)
145 self.keybinding.trace_add('write', self.var_changed_keybinding)
146 self.builtin_keys.trace_add('write', self.var_changed_builtin_keys)
147 self.custom_keys.trace_add('write', self.var_changed_custom_keys)
148 self.are_keys_builtin.trace_add('write', self.var_changed_are_keys_builtin)
149 self.win_width.trace_add('write', self.var_changed_win_width)
150 self.win_height.trace_add('write', self.var_changed_win_height)
151 self.startup_edit.trace_add('write', self.var_changed_startup_edit)
152 self.autosave.trace_add('write', self.var_changed_autosave)
153
154 def remove_var_callbacks(self):
155 "Remove callbacks to prevent memory leaks."
156 for var in (
157 self.font_size, self.font_name, self.font_bold,
158 self.space_num, self.color, self.builtin_theme,
159 self.custom_theme, self.is_builtin_theme, self.highlight_target,
160 self.keybinding, self.builtin_keys, self.custom_keys,
161 self.are_keys_builtin, self.win_width, self.win_height,
162 self.startup_edit, self.autosave,):
163 var.trace_remove('write', var.trace_info()[0][1])
164
165
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400166 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400167 """Return frame of action buttons for dialog.
168
169 Methods:
170 ok
171 apply
172 cancel
173 help
174
175 Widget Structure:
176 outer: Frame
177 buttons: Frame
178 (no assignment): Button (ok)
179 (no assignment): Button (apply)
180 (no assignment): Button (cancel)
181 (no assignment): Button (help)
182 (no assignment): Frame
183 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400184 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400185 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400186 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400187 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000188 else:
csabellabac7d332017-06-26 17:46:26 -0400189 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400190 outer = Frame(self, pady=2)
191 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400192 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400193 ('Ok', self.ok),
194 ('Apply', self.apply),
195 ('Cancel', self.cancel),
196 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400197 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
csabellabac7d332017-06-26 17:46:26 -0400198 **padding_args).pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400199 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400200 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
201 buttons.pack(side=BOTTOM)
202 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400203
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400204 def ok(self):
205 """Apply config changes, then dismiss dialog.
206
207 Methods:
208 apply
209 destroy: inherited
210 """
211 self.apply()
212 self.destroy()
213
214 def apply(self):
215 """Apply config changes and leave dialog open.
216
217 Methods:
218 deactivate_current_config
219 save_all_changed_extensions
220 activate_config_changes
221 """
222 self.deactivate_current_config()
223 changes.save_all()
224 self.save_all_changed_extensions()
225 self.activate_config_changes()
226
227 def cancel(self):
228 """Dismiss config dialog.
229
230 Methods:
231 destroy: inherited
232 """
233 self.destroy()
234
235 def help(self):
236 """Create textview for config dialog help.
237
238 Attrbutes accessed:
239 tab_pages
240
241 Methods:
242 view_text: Method from textview module.
243 """
244 page = self.tab_pages._current_page
245 view_text(self, title='Help for IDLE preferences',
246 text=help_common+help_pages.get(page, ''))
247
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400248
csabellabac7d332017-06-26 17:46:26 -0400249 def create_page_font_tab(self):
csabella7eb58832017-07-04 21:30:58 -0400250 """Return frame of widgets for Font/Tabs tab.
251
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400252 Fonts: Enable users to provisionally change font face, size, or
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400253 boldness and to see the consequence of proposed choices. Each
254 action set 3 options in changes structuree and changes the
255 corresponding aspect of the font sample on this page and
256 highlight sample on highlight page.
257
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400258 Funtion load_font_cfg initializes font vars and widgets from
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400259 idleConf entries and tk.
260
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400261 Fontlist: mouse button 1 click or up or down key invoke
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400262 on_fontlist_select(), which sets var font_name.
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400263
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400264 Sizelist: clicking the menubutton opens the dropdown menu. A
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400265 mouse button 1 click or return key sets var font_size.
csabella36329a42017-07-13 23:32:01 -0400266
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400267 Bold_toggle: clicking the box toggles var font_bold.
csabella36329a42017-07-13 23:32:01 -0400268
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400269 Changing any of the font vars invokes var_changed_font, which
270 adds all 3 font options to changes and calls set_samples.
271 Set_samples applies a new font constructed from the font vars to
272 font_sample and to highlight_sample on the hightlight page.
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400273
274 Tabs: Enable users to change spaces entered for indent tabs.
275 Changing indent_scale value with the mouse sets Var space_num,
276 which invokes var_changed_space_num, which adds an entry to
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400277 changes. Load_tab_cfg initializes space_num to default.
csabella36329a42017-07-13 23:32:01 -0400278
279 Widget Structure: (*) widgets bound to self
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400280 frame (of tab_pages)
csabella36329a42017-07-13 23:32:01 -0400281 frame_font: LabelFrame
282 frame_font_name: Frame
283 font_name_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400284 (*)fontlist: ListBox - font_name
csabella36329a42017-07-13 23:32:01 -0400285 scroll_font: Scrollbar
286 frame_font_param: Frame
287 font_size_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400288 (*)sizelist: DynOptionMenu - font_size
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400289 (*)bold_toggle: Checkbutton - font_bold
csabella36329a42017-07-13 23:32:01 -0400290 frame_font_sample: Frame
291 (*)font_sample: Label
292 frame_indent: LabelFrame
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400293 indent_title: Label
294 (*)indent_scale: Scale - space_num
csabella7eb58832017-07-04 21:30:58 -0400295 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400296 parent = self.parent
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400297 self.font_name = StringVar(parent)
csabellabac7d332017-06-26 17:46:26 -0400298 self.font_size = StringVar(parent)
299 self.font_bold = BooleanVar(parent)
csabellabac7d332017-06-26 17:46:26 -0400300 self.space_num = IntVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400301
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400302 # Create widgets:
Louie Lubb2bae82017-07-10 06:57:18 +0800303 # body and body section frames.
csabellabac7d332017-06-26 17:46:26 -0400304 frame = self.tab_pages.pages['Fonts/Tabs'].frame
csabellabac7d332017-06-26 17:46:26 -0400305 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400306 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
csabellabac7d332017-06-26 17:46:26 -0400307 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400308 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400309 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400310 frame_font_name = Frame(frame_font)
311 frame_font_param = Frame(frame_font)
312 font_name_title = Label(
313 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400314 self.fontlist = Listbox(frame_font_name, height=5,
315 takefocus=FALSE, exportselection=FALSE)
terryjreedy5b62b352017-07-11 01:58:04 -0400316 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
317 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
318 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
csabellabac7d332017-06-26 17:46:26 -0400319 scroll_font = Scrollbar(frame_font_name)
Louie Lubb2bae82017-07-10 06:57:18 +0800320 scroll_font.config(command=self.fontlist.yview)
321 self.fontlist.config(yscrollcommand=scroll_font.set)
csabellabac7d332017-06-26 17:46:26 -0400322 font_size_title = Label(frame_font_param, text='Size :')
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400323 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400324 self.bold_toggle = Checkbutton(
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400325 frame_font_param, variable=self.font_bold,
326 onvalue=1, offvalue=0, text='Bold')
csabellabac7d332017-06-26 17:46:26 -0400327 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400328 temp_font = tkFont.Font(parent, ('courier', 10, 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400329 self.font_sample = Label(
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400330 frame_font_sample, justify=LEFT, font=temp_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400331 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400332 # frame_indent.
333 indent_title = Label(
334 frame_indent, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400335 text='Python Standard: 4 Spaces!')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400336 self.indent_scale = Scale(
337 frame_indent, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400338 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400339
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400340 # Pack widgets:
341 # body.
csabellabac7d332017-06-26 17:46:26 -0400342 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
343 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400344 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400345 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
346 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
347 font_name_title.pack(side=TOP, anchor=W)
Louie Lubb2bae82017-07-10 06:57:18 +0800348 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400349 scroll_font.pack(side=LEFT, fill=Y)
350 font_size_title.pack(side=LEFT, anchor=W)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400351 self.sizelist.pack(side=LEFT, anchor=W)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400352 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
csabellabac7d332017-06-26 17:46:26 -0400353 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
354 self.font_sample.pack(expand=TRUE, fill=BOTH)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400355 # frame_indent.
356 frame_indent.pack(side=TOP, fill=X)
357 indent_title.pack(side=TOP, anchor=W, padx=5)
358 self.indent_scale.pack(side=TOP, padx=5, fill=X)
Louie Lubb2bae82017-07-10 06:57:18 +0800359
Steven M. Gava952d0a52001-08-03 04:43:44 +0000360 return frame
361
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400362 def load_font_cfg(self):
363 """Load current configuration settings for the font options.
364
365 Retrieve current font with idleConf.GetFont and font families
366 from tk. Setup fontlist and set font_name. Setup sizelist,
367 which sets font_size. Set font_bold. Setting font variables
368 calls set_samples (thrice).
369 """
370 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
371 font_name = configured_font[0].lower()
372 font_size = configured_font[1]
373 font_bold = configured_font[2]=='bold'
374
375 # Set editor font selection list and font_name.
376 fonts = list(tkFont.families(self))
377 fonts.sort()
378 for font in fonts:
379 self.fontlist.insert(END, font)
380 self.font_name.set(font_name)
381 lc_fonts = [s.lower() for s in fonts]
382 try:
383 current_font_index = lc_fonts.index(font_name)
384 self.fontlist.see(current_font_index)
385 self.fontlist.select_set(current_font_index)
386 self.fontlist.select_anchor(current_font_index)
387 self.fontlist.activate(current_font_index)
388 except ValueError:
389 pass
390 # Set font size dropdown.
391 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
392 '16', '18', '20', '22', '25', '29', '34', '40'),
393 font_size)
394 # Set font weight.
395 self.font_bold.set(font_bold)
396
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400397 def var_changed_font(self, *params):
398 """Store changes to font attributes.
399
400 When one font attribute changes, save them all, as they are
401 not independent from each other. In particular, when we are
402 overriding the default font, we need to write out everything.
403 """
404 value = self.font_name.get()
405 changes.add_option('main', 'EditorWindow', 'font', value)
406 value = self.font_size.get()
407 changes.add_option('main', 'EditorWindow', 'font-size', value)
408 value = self.font_bold.get()
409 changes.add_option('main', 'EditorWindow', 'font-bold', value)
410 self.set_samples()
411
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400412 def on_fontlist_select(self, event):
413 """Handle selecting a font from the list.
414
415 Event can result from either mouse click or Up or Down key.
416 Set font_name and example displays to selection.
417 """
418 font = self.fontlist.get(
419 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
420 self.font_name.set(font.lower())
421
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400422 def set_samples(self, event=None):
423 """Update update both screen samples with the font settings.
424
425 Called on font initialization and change events.
426 Accesses font_name, font_size, and font_bold Variables.
427 Updates font_sample and hightlight page highlight_sample.
428 """
429 font_name = self.font_name.get()
430 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
431 new_font = (font_name, self.font_size.get(), font_weight)
432 self.font_sample['font'] = new_font
433 self.highlight_sample['font'] = new_font
434
435 def load_tab_cfg(self):
436 """Load current configuration settings for the tab options.
437
438 Attributes updated:
439 space_num: Set to value from idleConf.
440 """
441 # Set indent sizes.
442 space_num = idleConf.GetOption(
443 'main', 'Indent', 'num-spaces', default=4, type='int')
444 self.space_num.set(space_num)
445
446 def var_changed_space_num(self, *params):
447 "Store change to indentation size."
448 value = self.space_num.get()
449 changes.add_option('main', 'Indent', 'num-spaces', value)
450
451
csabellabac7d332017-06-26 17:46:26 -0400452 def create_page_highlight(self):
csabella7eb58832017-07-04 21:30:58 -0400453 """Return frame of widgets for Highlighting tab.
454
csabella36329a42017-07-13 23:32:01 -0400455 Tk Variables:
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400456 color: Color of selected target.
csabella7eb58832017-07-04 21:30:58 -0400457 builtin_theme: Menu variable for built-in theme.
458 custom_theme: Menu variable for custom theme.
459 fg_bg_toggle: Toggle for foreground/background color.
csabella36329a42017-07-13 23:32:01 -0400460 Note: this has no callback.
csabella7eb58832017-07-04 21:30:58 -0400461 is_builtin_theme: Selector for built-in or custom theme.
462 highlight_target: Menu variable for the highlight tag target.
csabella36329a42017-07-13 23:32:01 -0400463
464 Instance Data Attributes:
465 theme_elements: Dictionary of tags for text highlighting.
466 The key is the display name and the value is a tuple of
467 (tag name, display sort order).
468
469 Methods [attachment]:
470 load_theme_cfg: Load current highlight colors.
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400471 get_color: Invoke colorchooser [button_set_color].
472 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
csabella36329a42017-07-13 23:32:01 -0400473 set_highlight_target: set fg_bg_toggle, set_color_sample().
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400474 set_color_sample: Set frame background to target.
475 on_new_color_set: Set new color and add option.
csabella36329a42017-07-13 23:32:01 -0400476 paint_theme_sample: Recolor sample.
477 get_new_theme_name: Get from popup.
478 create_new_theme: Combine theme with changes and save.
479 save_as_new_theme: Save [button_save_custom_theme].
480 set_theme_type: Command for [is_builtin_theme].
481 delete_custom_theme: Ativate default [button_delete_custom_theme].
482 save_new_theme: Save to userCfg['theme'] (is function).
483
484 Widget Structure: (*) widgets bound to self
485 frame
486 frame_custom: LabelFrame
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400487 (*)highlight_sample: Text
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400488 (*)frame_color_set: Frame
489 button_set_color: Button
csabella36329a42017-07-13 23:32:01 -0400490 (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
491 frame_fg_bg_toggle: Frame
492 (*)radio_fg: Radiobutton - fg_bg_toggle
493 (*)radio_bg: Radiobutton - fg_bg_toggle
494 button_save_custom_theme: Button
495 frame_theme: LabelFrame
496 theme_type_title: Label
497 (*)radio_theme_builtin: Radiobutton - is_builtin_theme
498 (*)radio_theme_custom: Radiobutton - is_builtin_theme
499 (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
500 (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
501 (*)button_delete_custom_theme: Button
502 (*)new_custom_theme: Label
csabella7eb58832017-07-04 21:30:58 -0400503 """
csabella36329a42017-07-13 23:32:01 -0400504 self.theme_elements={
505 'Normal Text': ('normal', '00'),
506 'Python Keywords': ('keyword', '01'),
507 'Python Definitions': ('definition', '02'),
508 'Python Builtins': ('builtin', '03'),
509 'Python Comments': ('comment', '04'),
510 'Python Strings': ('string', '05'),
511 'Selected Text': ('hilite', '06'),
512 'Found Text': ('hit', '07'),
513 'Cursor': ('cursor', '08'),
514 'Editor Breakpoint': ('break', '09'),
515 'Shell Normal Text': ('console', '10'),
516 'Shell Error Text': ('error', '11'),
517 'Shell Stdout Text': ('stdout', '12'),
518 'Shell Stderr Text': ('stderr', '13'),
519 }
Terry Jan Reedy22405332014-07-30 19:24:32 -0400520 parent = self.parent
csabellabac7d332017-06-26 17:46:26 -0400521 self.builtin_theme = StringVar(parent)
522 self.custom_theme = StringVar(parent)
523 self.fg_bg_toggle = BooleanVar(parent)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400524 self.color = StringVar(parent)
csabellabac7d332017-06-26 17:46:26 -0400525 self.is_builtin_theme = BooleanVar(parent)
526 self.highlight_target = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400527
Steven M. Gava952d0a52001-08-03 04:43:44 +0000528 ##widget creation
529 #body frame
csabellabac7d332017-06-26 17:46:26 -0400530 frame = self.tab_pages.pages['Highlighting'].frame
Steven M. Gava952d0a52001-08-03 04:43:44 +0000531 #body section frames
csabellabac7d332017-06-26 17:46:26 -0400532 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400533 text=' Custom Highlighting ')
csabellabac7d332017-06-26 17:46:26 -0400534 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400535 text=' Highlighting Theme ')
csabellabac7d332017-06-26 17:46:26 -0400536 #frame_custom
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400537 self.highlight_sample=Text(
csabellabac7d332017-06-26 17:46:26 -0400538 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400539 font=('courier', 12, ''), cursor='hand2', width=21, height=11,
540 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400541 text=self.highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400542 text.bind('<Double-Button-1>', lambda e: 'break')
543 text.bind('<B1-Motion>', lambda e: 'break')
csabellabac7d332017-06-26 17:46:26 -0400544 text_and_tags=(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400545 ('#you can click here', 'comment'), ('\n', 'normal'),
546 ('#to choose items', 'comment'), ('\n', 'normal'),
547 ('def', 'keyword'), (' ', 'normal'),
548 ('func', 'definition'), ('(param):\n ', 'normal'),
549 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
550 ("'string'", 'string'), ('\n var1 = ', 'normal'),
551 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
552 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
553 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400554 ('None', 'keyword'), (')\n', 'normal'),
555 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400556 (' error ', 'error'), (' ', 'normal'),
557 ('cursor |', 'cursor'), ('\n ', 'normal'),
558 ('shell', 'console'), (' ', 'normal'),
559 ('stdout', 'stdout'), (' ', 'normal'),
560 ('stderr', 'stderr'), ('\n', 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400561 for texttag in text_and_tags:
562 text.insert(END, texttag[0], texttag[1])
563 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400564 def tem(event, elem=element):
csabellabac7d332017-06-26 17:46:26 -0400565 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400566 text.tag_bind(
csabellabac7d332017-06-26 17:46:26 -0400567 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400568 text['state'] = DISABLED
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400569 self.frame_color_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
csabellabac7d332017-06-26 17:46:26 -0400570 frame_fg_bg_toggle = Frame(frame_custom)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400571 button_set_color = Button(
572 self.frame_color_set, text='Choose Color for :',
573 command=self.get_color, highlightthickness=0)
csabellabac7d332017-06-26 17:46:26 -0400574 self.opt_menu_highlight_target = DynOptionMenu(
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400575 self.frame_color_set, self.highlight_target, None,
csabellabac7d332017-06-26 17:46:26 -0400576 highlightthickness=0) #, command=self.set_highlight_targetBinding
577 self.radio_fg = Radiobutton(
578 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400579 text='Foreground', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400580 self.radio_bg=Radiobutton(
581 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400582 text='Background', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400583 self.fg_bg_toggle.set(1)
584 button_save_custom_theme = Button(
585 frame_custom, text='Save as New Custom Theme',
586 command=self.save_as_new_theme)
587 #frame_theme
588 theme_type_title = Label(frame_theme, text='Select : ')
589 self.radio_theme_builtin = Radiobutton(
590 frame_theme, variable=self.is_builtin_theme, value=1,
591 command=self.set_theme_type, text='a Built-in Theme')
592 self.radio_theme_custom = Radiobutton(
593 frame_theme, variable=self.is_builtin_theme, value=0,
594 command=self.set_theme_type, text='a Custom Theme')
595 self.opt_menu_theme_builtin = DynOptionMenu(
596 frame_theme, self.builtin_theme, None, command=None)
597 self.opt_menu_theme_custom=DynOptionMenu(
598 frame_theme, self.custom_theme, None, command=None)
599 self.button_delete_custom_theme=Button(
600 frame_theme, text='Delete Custom Theme',
601 command=self.delete_custom_theme)
602 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400603
Steven M. Gava952d0a52001-08-03 04:43:44 +0000604 ##widget packing
605 #body
csabellabac7d332017-06-26 17:46:26 -0400606 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
607 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
608 #frame_custom
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400609 self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400610 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400611 self.highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400612 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400613 button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
csabellabac7d332017-06-26 17:46:26 -0400614 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400615 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
csabellabac7d332017-06-26 17:46:26 -0400616 self.radio_fg.pack(side=LEFT, anchor=E)
617 self.radio_bg.pack(side=RIGHT, anchor=W)
618 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
619 #frame_theme
620 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
621 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
622 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
623 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
624 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
625 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500626 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000627 return frame
628
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400629 def load_theme_cfg(self):
630 """Load current configuration settings for the theme options.
631
632 Based on the is_builtin_theme toggle, the theme is set as
633 either builtin or custom and the initial widget values
634 reflect the current settings from idleConf.
635
636 Attributes updated:
637 is_builtin_theme: Set from idleConf.
638 opt_menu_theme_builtin: List of default themes from idleConf.
639 opt_menu_theme_custom: List of custom themes from idleConf.
640 radio_theme_custom: Disabled if there are no custom themes.
641 custom_theme: Message with additional information.
642 opt_menu_highlight_target: Create menu from self.theme_elements.
643
644 Methods:
645 set_theme_type
646 paint_theme_sample
647 set_highlight_target
648 """
649 # Set current theme type radiobutton.
650 self.is_builtin_theme.set(idleConf.GetOption(
651 'main', 'Theme', 'default', type='bool', default=1))
652 # Set current theme.
653 current_option = idleConf.CurrentTheme()
654 # Load available theme option menus.
655 if self.is_builtin_theme.get(): # Default theme selected.
656 item_list = idleConf.GetSectionList('default', 'highlight')
657 item_list.sort()
658 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
659 item_list = idleConf.GetSectionList('user', 'highlight')
660 item_list.sort()
661 if not item_list:
662 self.radio_theme_custom['state'] = DISABLED
663 self.custom_theme.set('- no custom themes -')
664 else:
665 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
666 else: # User theme selected.
667 item_list = idleConf.GetSectionList('user', 'highlight')
668 item_list.sort()
669 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
670 item_list = idleConf.GetSectionList('default', 'highlight')
671 item_list.sort()
672 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
673 self.set_theme_type()
674 # Load theme element option menu.
675 theme_names = list(self.theme_elements.keys())
676 theme_names.sort(key=lambda x: self.theme_elements[x][1])
677 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
678 self.paint_theme_sample()
679 self.set_highlight_target()
680
681 def var_changed_builtin_theme(self, *params):
682 """Process new builtin theme selection.
683
684 Add the changed theme's name to the changed_items and recreate
685 the sample with the values from the selected theme.
686 """
687 old_themes = ('IDLE Classic', 'IDLE New')
688 value = self.builtin_theme.get()
689 if value not in old_themes:
690 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
691 changes.add_option('main', 'Theme', 'name', old_themes[0])
692 changes.add_option('main', 'Theme', 'name2', value)
693 self.new_custom_theme.config(text='New theme, see Help',
694 fg='#500000')
695 else:
696 changes.add_option('main', 'Theme', 'name', value)
697 changes.add_option('main', 'Theme', 'name2', '')
698 self.new_custom_theme.config(text='', fg='black')
699 self.paint_theme_sample()
700
701 def var_changed_custom_theme(self, *params):
702 """Process new custom theme selection.
703
704 If a new custom theme is selected, add the name to the
705 changed_items and apply the theme to the sample.
706 """
707 value = self.custom_theme.get()
708 if value != '- no custom themes -':
709 changes.add_option('main', 'Theme', 'name', value)
710 self.paint_theme_sample()
711
712 def var_changed_is_builtin_theme(self, *params):
713 """Process toggle between builtin and custom theme.
714
715 Update the default toggle value and apply the newly
716 selected theme type.
717 """
718 value = self.is_builtin_theme.get()
719 changes.add_option('main', 'Theme', 'default', value)
720 if value:
721 self.var_changed_builtin_theme()
722 else:
723 self.var_changed_custom_theme()
724
725 def var_changed_color(self, *params):
726 "Process change to color choice."
727 self.on_new_color_set()
728
729 def var_changed_highlight_target(self, *params):
730 "Process selection of new target tag for highlighting."
731 self.set_highlight_target()
732
733 def set_theme_type(self):
734 """Set available screen options based on builtin or custom theme.
735
736 Attributes accessed:
737 is_builtin_theme
738
739 Attributes updated:
740 opt_menu_theme_builtin
741 opt_menu_theme_custom
742 button_delete_custom_theme
743 radio_theme_custom
744
745 Called from:
746 handler for radio_theme_builtin and radio_theme_custom
747 delete_custom_theme
748 create_new_theme
749 load_theme_cfg
750 """
751 if self.is_builtin_theme.get():
752 self.opt_menu_theme_builtin['state'] = NORMAL
753 self.opt_menu_theme_custom['state'] = DISABLED
754 self.button_delete_custom_theme['state'] = DISABLED
755 else:
756 self.opt_menu_theme_builtin['state'] = DISABLED
757 self.radio_theme_custom['state'] = NORMAL
758 self.opt_menu_theme_custom['state'] = NORMAL
759 self.button_delete_custom_theme['state'] = NORMAL
760
761 def get_color(self):
762 """Handle button to select a new color for the target tag.
763
764 If a new color is selected while using a builtin theme, a
765 name must be supplied to create a custom theme.
766
767 Attributes accessed:
768 highlight_target
769 frame_color_set
770 is_builtin_theme
771
772 Attributes updated:
773 color
774
775 Methods:
776 get_new_theme_name
777 create_new_theme
778 """
779 target = self.highlight_target.get()
780 prev_color = self.frame_color_set.cget('bg')
781 rgbTuplet, color_string = tkColorChooser.askcolor(
782 parent=self, title='Pick new color for : '+target,
783 initialcolor=prev_color)
784 if color_string and (color_string != prev_color):
785 # User didn't cancel and they chose a new color.
786 if self.is_builtin_theme.get(): # Current theme is a built-in.
787 message = ('Your changes will be saved as a new Custom Theme. '
788 'Enter a name for your new Custom Theme below.')
789 new_theme = self.get_new_theme_name(message)
790 if not new_theme: # User cancelled custom theme creation.
791 return
792 else: # Create new custom theme based on previously active theme.
793 self.create_new_theme(new_theme)
794 self.color.set(color_string)
795 else: # Current theme is user defined.
796 self.color.set(color_string)
797
798 def on_new_color_set(self):
799 "Display sample of new color selection on the dialog."
800 new_color=self.color.get()
801 self.frame_color_set.config(bg=new_color) # Set sample.
802 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
803 sample_element = self.theme_elements[self.highlight_target.get()][0]
804 self.highlight_sample.tag_config(sample_element, **{plane:new_color})
805 theme = self.custom_theme.get()
806 theme_element = sample_element + '-' + plane
807 changes.add_option('highlight', theme, theme_element, new_color)
808
809 def get_new_theme_name(self, message):
810 "Return name of new theme from query popup."
811 used_names = (idleConf.GetSectionList('user', 'highlight') +
812 idleConf.GetSectionList('default', 'highlight'))
813 new_theme = SectionName(
814 self, 'New Custom Theme', message, used_names).result
815 return new_theme
816
817 def save_as_new_theme(self):
818 """Prompt for new theme name and create the theme.
819
820 Methods:
821 get_new_theme_name
822 create_new_theme
823 """
824 new_theme_name = self.get_new_theme_name('New Theme Name:')
825 if new_theme_name:
826 self.create_new_theme(new_theme_name)
827
828 def create_new_theme(self, new_theme_name):
829 """Create a new custom theme with the given name.
830
831 Create the new theme based on the previously active theme
832 with the current changes applied. Once it is saved, then
833 activate the new theme.
834
835 Attributes accessed:
836 builtin_theme
837 custom_theme
838
839 Attributes updated:
840 opt_menu_theme_custom
841 is_builtin_theme
842
843 Method:
844 save_new_theme
845 set_theme_type
846 """
847 if self.is_builtin_theme.get():
848 theme_type = 'default'
849 theme_name = self.builtin_theme.get()
850 else:
851 theme_type = 'user'
852 theme_name = self.custom_theme.get()
853 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
854 # Apply any of the old theme's unsaved changes to the new theme.
855 if theme_name in changes['highlight']:
856 theme_changes = changes['highlight'][theme_name]
857 for element in theme_changes:
858 new_theme[element] = theme_changes[element]
859 # Save the new theme.
860 self.save_new_theme(new_theme_name, new_theme)
861 # Change GUI over to the new theme.
862 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
863 custom_theme_list.sort()
864 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
865 self.is_builtin_theme.set(0)
866 self.set_theme_type()
867
868 def set_highlight_target(self):
869 """Set fg/bg toggle and color based on highlight tag target.
870
871 Instance variables accessed:
872 highlight_target
873
874 Attributes updated:
875 radio_fg
876 radio_bg
877 fg_bg_toggle
878
879 Methods:
880 set_color_sample
881
882 Called from:
883 var_changed_highlight_target
884 load_theme_cfg
885 """
886 if self.highlight_target.get() == 'Cursor': # bg not possible
887 self.radio_fg['state'] = DISABLED
888 self.radio_bg['state'] = DISABLED
889 self.fg_bg_toggle.set(1)
890 else: # Both fg and bg can be set.
891 self.radio_fg['state'] = NORMAL
892 self.radio_bg['state'] = NORMAL
893 self.fg_bg_toggle.set(1)
894 self.set_color_sample()
895
896 def set_color_sample_binding(self, *args):
897 """Change color sample based on foreground/background toggle.
898
899 Methods:
900 set_color_sample
901 """
902 self.set_color_sample()
903
904 def set_color_sample(self):
905 """Set the color of the frame background to reflect the selected target.
906
907 Instance variables accessed:
908 theme_elements
909 highlight_target
910 fg_bg_toggle
911 highlight_sample
912
913 Attributes updated:
914 frame_color_set
915 """
916 # Set the color sample area.
917 tag = self.theme_elements[self.highlight_target.get()][0]
918 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
919 color = self.highlight_sample.tag_cget(tag, plane)
920 self.frame_color_set.config(bg=color)
921
922 def paint_theme_sample(self):
923 """Apply the theme colors to each element tag in the sample text.
924
925 Instance attributes accessed:
926 theme_elements
927 is_builtin_theme
928 builtin_theme
929 custom_theme
930
931 Attributes updated:
932 highlight_sample: Set the tag elements to the theme.
933
934 Methods:
935 set_color_sample
936
937 Called from:
938 var_changed_builtin_theme
939 var_changed_custom_theme
940 load_theme_cfg
941 """
942 if self.is_builtin_theme.get(): # Default theme
943 theme = self.builtin_theme.get()
944 else: # User theme
945 theme = self.custom_theme.get()
946 for element_title in self.theme_elements:
947 element = self.theme_elements[element_title][0]
948 colors = idleConf.GetHighlight(theme, element)
949 if element == 'cursor': # Cursor sample needs special painting.
950 colors['background'] = idleConf.GetHighlight(
951 theme, 'normal', fgBg='bg')
952 # Handle any unsaved changes to this theme.
953 if theme in changes['highlight']:
954 theme_dict = changes['highlight'][theme]
955 if element + '-foreground' in theme_dict:
956 colors['foreground'] = theme_dict[element + '-foreground']
957 if element + '-background' in theme_dict:
958 colors['background'] = theme_dict[element + '-background']
959 self.highlight_sample.tag_config(element, **colors)
960 self.set_color_sample()
961
962 def save_new_theme(self, theme_name, theme):
963 """Save a newly created theme to idleConf.
964
965 theme_name - string, the name of the new theme
966 theme - dictionary containing the new theme
967 """
968 if not idleConf.userCfg['highlight'].has_section(theme_name):
969 idleConf.userCfg['highlight'].add_section(theme_name)
970 for element in theme:
971 value = theme[element]
972 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
973
974 def delete_custom_theme(self):
975 """Handle event to delete custom theme.
976
977 The current theme is deactivated and the default theme is
978 activated. The custom theme is permanently removed from
979 the config file.
980
981 Attributes accessed:
982 custom_theme
983
984 Attributes updated:
985 radio_theme_custom
986 opt_menu_theme_custom
987 is_builtin_theme
988 builtin_theme
989
990 Methods:
991 deactivate_current_config
992 save_all_changed_extensions
993 activate_config_changes
994 set_theme_type
995 """
996 theme_name = self.custom_theme.get()
997 delmsg = 'Are you sure you wish to delete the theme %r ?'
998 if not tkMessageBox.askyesno(
999 'Delete Theme', delmsg % theme_name, parent=self):
1000 return
1001 self.deactivate_current_config()
1002 # Remove theme from changes, config, and file.
1003 changes.delete_section('highlight', theme_name)
1004 # Reload user theme list.
1005 item_list = idleConf.GetSectionList('user', 'highlight')
1006 item_list.sort()
1007 if not item_list:
1008 self.radio_theme_custom['state'] = DISABLED
1009 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
1010 else:
1011 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
1012 # Revert to default theme.
1013 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
1014 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
1015 # User can't back out of these changes, they must be applied now.
1016 changes.save_all()
1017 self.save_all_changed_extensions()
1018 self.activate_config_changes()
1019 self.set_theme_type()
1020
1021
csabellabac7d332017-06-26 17:46:26 -04001022 def create_page_keys(self):
csabella7eb58832017-07-04 21:30:58 -04001023 """Return frame of widgets for Keys tab.
1024
csabella36329a42017-07-13 23:32:01 -04001025 Tk Variables:
csabella7eb58832017-07-04 21:30:58 -04001026 builtin_keys: Menu variable for built-in keybindings.
1027 custom_keys: Menu variable for custom keybindings.
1028 are_keys_builtin: Selector for built-in or custom keybindings.
1029 keybinding: Action/key bindings.
csabella36329a42017-07-13 23:32:01 -04001030
1031 Methods:
1032 load_key_config: Set table.
1033 load_keys_list: Reload active set.
1034 keybinding_selected: Bound to list_bindings button release.
1035 get_new_keys: Command for button_new_keys.
1036 get_new_keys_name: Call popup.
1037 create_new_key_set: Combine active keyset and changes.
1038 set_keys_type: Command for are_keys_builtin.
1039 delete_custom_keys: Command for button_delete_custom_keys.
1040 save_as_new_key_set: Command for button_save_custom_keys.
1041 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1042 deactivate_current_config: Remove keys bindings in editors.
1043
1044 Widget Structure: (*) widgets bound to self
1045 frame
1046 frame_custom: LabelFrame
1047 frame_target: Frame
1048 target_title: Label
1049 scroll_target_y: Scrollbar
1050 scroll_target_x: Scrollbar
1051 (*)list_bindings: ListBox
1052 (*)button_new_keys: Button
1053 frame_key_sets: LabelFrame
1054 frames[0]: Frame
1055 (*)radio_keys_builtin: Radiobutton - are_keys_builtin
1056 (*)radio_keys_custom: Radiobutton - are_keys_builtin
1057 (*)opt_menu_keys_builtin: DynOptionMenu - builtin_keys
1058 (*)opt_menu_keys_custom: DynOptionMenu - custom_keys
1059 (*)new_custom_keys: Label
1060 frames[1]: Frame
1061 (*)button_delete_custom_keys: Button
1062 button_save_custom_keys: Button
csabella7eb58832017-07-04 21:30:58 -04001063 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001064 parent = self.parent
csabellabac7d332017-06-26 17:46:26 -04001065 self.builtin_keys = StringVar(parent)
1066 self.custom_keys = StringVar(parent)
1067 self.are_keys_builtin = BooleanVar(parent)
1068 self.keybinding = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001069
Steven M. Gava60fc7072001-08-04 13:58:22 +00001070 ##widget creation
1071 #body frame
csabellabac7d332017-06-26 17:46:26 -04001072 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +00001073 #body section frames
csabellabac7d332017-06-26 17:46:26 -04001074 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001075 frame, borderwidth=2, relief=GROOVE,
1076 text=' Custom Key Bindings ')
csabellabac7d332017-06-26 17:46:26 -04001077 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001078 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
csabellabac7d332017-06-26 17:46:26 -04001079 #frame_custom
1080 frame_target = Frame(frame_custom)
1081 target_title = Label(frame_target, text='Action - Key(s)')
1082 scroll_target_y = Scrollbar(frame_target)
1083 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1084 self.list_bindings = Listbox(
1085 frame_target, takefocus=FALSE, exportselection=FALSE)
1086 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
1087 scroll_target_y.config(command=self.list_bindings.yview)
1088 scroll_target_x.config(command=self.list_bindings.xview)
1089 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
1090 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
1091 self.button_new_keys = Button(
1092 frame_custom, text='Get New Keys for Selection',
1093 command=self.get_new_keys, state=DISABLED)
1094 #frame_key_sets
1095 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +00001096 for i in range(2)]
csabellabac7d332017-06-26 17:46:26 -04001097 self.radio_keys_builtin = Radiobutton(
1098 frames[0], variable=self.are_keys_builtin, value=1,
1099 command=self.set_keys_type, text='Use a Built-in Key Set')
1100 self.radio_keys_custom = Radiobutton(
1101 frames[0], variable=self.are_keys_builtin, value=0,
1102 command=self.set_keys_type, text='Use a Custom Key Set')
1103 self.opt_menu_keys_builtin = DynOptionMenu(
1104 frames[0], self.builtin_keys, None, command=None)
1105 self.opt_menu_keys_custom = DynOptionMenu(
1106 frames[0], self.custom_keys, None, command=None)
1107 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001108 frames[1], text='Delete Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001109 command=self.delete_custom_keys)
1110 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001111 frames[1], text='Save as New Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001112 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001113 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001114
Steven M. Gava60fc7072001-08-04 13:58:22 +00001115 ##widget packing
1116 #body
csabellabac7d332017-06-26 17:46:26 -04001117 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1118 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1119 #frame_custom
1120 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1121 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +00001122 #frame target
csabellabac7d332017-06-26 17:46:26 -04001123 frame_target.columnconfigure(0, weight=1)
1124 frame_target.rowconfigure(1, weight=1)
1125 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1126 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
1127 scroll_target_y.grid(row=1, column=1, sticky=NS)
1128 scroll_target_x.grid(row=2, column=0, sticky=EW)
1129 #frame_key_sets
1130 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
1131 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
1132 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
1133 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001134 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001135 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1136 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +00001137 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1138 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +00001139 return frame
1140
Terry Jan Reedyb1660802017-07-27 18:28:01 -04001141 def load_key_cfg(self):
1142 "Load current configuration settings for the keybinding options."
1143 # Set current keys type radiobutton.
1144 self.are_keys_builtin.set(idleConf.GetOption(
1145 'main', 'Keys', 'default', type='bool', default=1))
1146 # Set current keys.
1147 current_option = idleConf.CurrentKeys()
1148 # Load available keyset option menus.
1149 if self.are_keys_builtin.get(): # Default theme selected.
1150 item_list = idleConf.GetSectionList('default', 'keys')
1151 item_list.sort()
1152 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1153 item_list = idleConf.GetSectionList('user', 'keys')
1154 item_list.sort()
1155 if not item_list:
1156 self.radio_keys_custom['state'] = DISABLED
1157 self.custom_keys.set('- no custom keys -')
1158 else:
1159 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1160 else: # User key set selected.
1161 item_list = idleConf.GetSectionList('user', 'keys')
1162 item_list.sort()
1163 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1164 item_list = idleConf.GetSectionList('default', 'keys')
1165 item_list.sort()
1166 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1167 self.set_keys_type()
1168 # Load keyset element list.
1169 keyset_name = idleConf.CurrentKeys()
1170 self.load_keys_list(keyset_name)
1171
1172
1173
1174
1175 def var_changed_builtin_keys(self, *params):
1176 "Process selection of builtin key set."
1177 old_keys = (
1178 'IDLE Classic Windows',
1179 'IDLE Classic Unix',
1180 'IDLE Classic Mac',
1181 'IDLE Classic OSX',
1182 )
1183 value = self.builtin_keys.get()
1184 if value not in old_keys:
1185 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1186 changes.add_option('main', 'Keys', 'name', old_keys[0])
1187 changes.add_option('main', 'Keys', 'name2', value)
1188 self.new_custom_keys.config(text='New key set, see Help',
1189 fg='#500000')
1190 else:
1191 changes.add_option('main', 'Keys', 'name', value)
1192 changes.add_option('main', 'Keys', 'name2', '')
1193 self.new_custom_keys.config(text='', fg='black')
1194 self.load_keys_list(value)
1195
1196 def var_changed_custom_keys(self, *params):
1197 "Process selection of custom key set."
1198 value = self.custom_keys.get()
1199 if value != '- no custom keys -':
1200 changes.add_option('main', 'Keys', 'name', value)
1201 self.load_keys_list(value)
1202
1203 def var_changed_are_keys_builtin(self, *params):
1204 "Process toggle between builtin key set and custom key set."
1205 value = self.are_keys_builtin.get()
1206 changes.add_option('main', 'Keys', 'default', value)
1207 if value:
1208 self.var_changed_builtin_keys()
1209 else:
1210 self.var_changed_custom_keys()
1211
1212 def var_changed_keybinding(self, *params):
1213 "Store change to a keybinding."
1214 value = self.keybinding.get()
1215 key_set = self.custom_keys.get()
1216 event = self.list_bindings.get(ANCHOR).split()[0]
1217 if idleConf.IsCoreBinding(event):
1218 changes.add_option('keys', key_set, event, value)
1219 else: # Event is an extension binding.
1220 ext_name = idleConf.GetExtnNameForEvent(event)
1221 ext_keybind_section = ext_name + '_cfgBindings'
1222 changes.add_option('extensions', ext_keybind_section, event, value)
1223
1224 def set_keys_type(self):
1225 "Set available screen options based on builtin or custom key set."
1226 if self.are_keys_builtin.get():
1227 self.opt_menu_keys_builtin['state'] = NORMAL
1228 self.opt_menu_keys_custom['state'] = DISABLED
1229 self.button_delete_custom_keys['state'] = DISABLED
1230 else:
1231 self.opt_menu_keys_builtin['state'] = DISABLED
1232 self.radio_keys_custom['state'] = NORMAL
1233 self.opt_menu_keys_custom['state'] = NORMAL
1234 self.button_delete_custom_keys['state'] = NORMAL
1235
1236 def get_new_keys(self):
1237 """Handle event to change key binding for selected line.
1238
1239 A selection of a key/binding in the list of current
1240 bindings pops up a dialog to enter a new binding. If
1241 the current key set is builtin and a binding has
1242 changed, then a name for a custom key set needs to be
1243 entered for the change to be applied.
1244 """
1245 list_index = self.list_bindings.index(ANCHOR)
1246 binding = self.list_bindings.get(list_index)
1247 bind_name = binding.split()[0]
1248 if self.are_keys_builtin.get():
1249 current_key_set_name = self.builtin_keys.get()
1250 else:
1251 current_key_set_name = self.custom_keys.get()
1252 current_bindings = idleConf.GetCurrentKeySet()
1253 if current_key_set_name in changes['keys']: # unsaved changes
1254 key_set_changes = changes['keys'][current_key_set_name]
1255 for event in key_set_changes:
1256 current_bindings[event] = key_set_changes[event].split()
1257 current_key_sequences = list(current_bindings.values())
1258 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1259 current_key_sequences).result
1260 if new_keys:
1261 if self.are_keys_builtin.get(): # Current key set is a built-in.
1262 message = ('Your changes will be saved as a new Custom Key Set.'
1263 ' Enter a name for your new Custom Key Set below.')
1264 new_keyset = self.get_new_keys_name(message)
1265 if not new_keyset: # User cancelled custom key set creation.
1266 self.list_bindings.select_set(list_index)
1267 self.list_bindings.select_anchor(list_index)
1268 return
1269 else: # Create new custom key set based on previously active key set.
1270 self.create_new_key_set(new_keyset)
1271 self.list_bindings.delete(list_index)
1272 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
1273 self.list_bindings.select_set(list_index)
1274 self.list_bindings.select_anchor(list_index)
1275 self.keybinding.set(new_keys)
1276 else:
1277 self.list_bindings.select_set(list_index)
1278 self.list_bindings.select_anchor(list_index)
1279
1280 def get_new_keys_name(self, message):
1281 "Return new key set name from query popup."
1282 used_names = (idleConf.GetSectionList('user', 'keys') +
1283 idleConf.GetSectionList('default', 'keys'))
1284 new_keyset = SectionName(
1285 self, 'New Custom Key Set', message, used_names).result
1286 return new_keyset
1287
1288 def save_as_new_key_set(self):
1289 "Prompt for name of new key set and save changes using that name."
1290 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1291 if new_keys_name:
1292 self.create_new_key_set(new_keys_name)
1293
1294 def keybinding_selected(self, event):
1295 "Activate button to assign new keys to selected action."
1296 self.button_new_keys['state'] = NORMAL
1297
1298 def create_new_key_set(self, new_key_set_name):
1299 """Create a new custom key set with the given name.
1300
1301 Create the new key set based on the previously active set
1302 with the current changes applied. Once it is saved, then
1303 activate the new key set.
1304 """
1305 if self.are_keys_builtin.get():
1306 prev_key_set_name = self.builtin_keys.get()
1307 else:
1308 prev_key_set_name = self.custom_keys.get()
1309 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1310 new_keys = {}
1311 for event in prev_keys: # Add key set to changed items.
1312 event_name = event[2:-2] # Trim off the angle brackets.
1313 binding = ' '.join(prev_keys[event])
1314 new_keys[event_name] = binding
1315 # Handle any unsaved changes to prev key set.
1316 if prev_key_set_name in changes['keys']:
1317 key_set_changes = changes['keys'][prev_key_set_name]
1318 for event in key_set_changes:
1319 new_keys[event] = key_set_changes[event]
1320 # Save the new key set.
1321 self.save_new_key_set(new_key_set_name, new_keys)
1322 # Change GUI over to the new key set.
1323 custom_key_list = idleConf.GetSectionList('user', 'keys')
1324 custom_key_list.sort()
1325 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
1326 self.are_keys_builtin.set(0)
1327 self.set_keys_type()
1328
1329 def load_keys_list(self, keyset_name):
1330 """Reload the list of action/key binding pairs for the active key set.
1331
1332 An action/key binding can be selected to change the key binding.
1333 """
1334 reselect = 0
1335 if self.list_bindings.curselection():
1336 reselect = 1
1337 list_index = self.list_bindings.index(ANCHOR)
1338 keyset = idleConf.GetKeySet(keyset_name)
1339 bind_names = list(keyset.keys())
1340 bind_names.sort()
1341 self.list_bindings.delete(0, END)
1342 for bind_name in bind_names:
1343 key = ' '.join(keyset[bind_name])
1344 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1345 if keyset_name in changes['keys']:
1346 # Handle any unsaved changes to this key set.
1347 if bind_name in changes['keys'][keyset_name]:
1348 key = changes['keys'][keyset_name][bind_name]
1349 self.list_bindings.insert(END, bind_name+' - '+key)
1350 if reselect:
1351 self.list_bindings.see(list_index)
1352 self.list_bindings.select_set(list_index)
1353 self.list_bindings.select_anchor(list_index)
1354
1355 def save_new_key_set(self, keyset_name, keyset):
1356 """Save a newly created core key set.
1357
1358 keyset_name - string, the name of the new key set
1359 keyset - dictionary containing the new key set
1360 """
1361 if not idleConf.userCfg['keys'].has_section(keyset_name):
1362 idleConf.userCfg['keys'].add_section(keyset_name)
1363 for event in keyset:
1364 value = keyset[event]
1365 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1366
1367 def delete_custom_keys(self):
1368 """Handle event to delete a custom key set.
1369
1370 Applying the delete deactivates the current configuration and
1371 reverts to the default. The custom key set is permanently
1372 deleted from the config file.
1373 """
1374 keyset_name=self.custom_keys.get()
1375 delmsg = 'Are you sure you wish to delete the key set %r ?'
1376 if not tkMessageBox.askyesno(
1377 'Delete Key Set', delmsg % keyset_name, parent=self):
1378 return
1379 self.deactivate_current_config()
1380 # Remove key set from changes, config, and file.
1381 changes.delete_section('keys', keyset_name)
1382 # Reload user key set list.
1383 item_list = idleConf.GetSectionList('user', 'keys')
1384 item_list.sort()
1385 if not item_list:
1386 self.radio_keys_custom['state'] = DISABLED
1387 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
1388 else:
1389 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1390 # Revert to default key set.
1391 self.are_keys_builtin.set(idleConf.defaultCfg['main']
1392 .Get('Keys', 'default'))
1393 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
1394 or idleConf.default_keys())
1395 # User can't back out of these changes, they must be applied now.
1396 changes.save_all()
1397 self.save_all_changed_extensions()
1398 self.activate_config_changes()
1399 self.set_keys_type()
1400
1401 def deactivate_current_config(self):
1402 """Remove current key bindings.
1403
1404 Iterate over window instances defined in parent and remove
1405 the keybindings.
1406 """
1407 # Before a config is saved, some cleanup of current
1408 # config must be done - remove the previous keybindings.
1409 win_instances = self.parent.instance_dict.keys()
1410 for instance in win_instances:
1411 instance.RemoveKeybindings()
1412
1413 def activate_config_changes(self):
1414 """Apply configuration changes to current windows.
1415
1416 Dynamically update the current parent window instances
1417 with some of the configuration changes.
1418 """
1419 win_instances = self.parent.instance_dict.keys()
1420 for instance in win_instances:
1421 instance.ResetColorizer()
1422 instance.ResetFont()
1423 instance.set_notabs_indentwidth()
1424 instance.ApplyKeybindings()
1425 instance.reset_help_menu_entries()
1426
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001427
csabellabac7d332017-06-26 17:46:26 -04001428 def create_page_general(self):
csabella7eb58832017-07-04 21:30:58 -04001429 """Return frame of widgets for General tab.
1430
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001431 Enable users to provisionally change general options. Function
1432 load_general_cfg intializes tk variables and helplist using
1433 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1434 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1435 set var autosave. Entry boxes win_width_int and win_height_int
1436 set var win_width and win_height. Setting var_name invokes the
1437 var_changed_var_name callback that adds option to changes.
csabella36329a42017-07-13 23:32:01 -04001438
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001439 Helplist: load_general_cfg loads list user_helplist with
1440 name, position pairs and copies names to listbox helplist.
1441 Clicking a name invokes help_source selected. Clicking
1442 button_helplist_name invokes helplist_item_name, which also
1443 changes user_helplist. These functions all call
1444 set_add_delete_state. All but load call update_help_changes to
1445 rewrite changes['main']['HelpFiles'].
csabella36329a42017-07-13 23:32:01 -04001446
1447 Widget Structure: (*) widgets bound to self
1448 frame
1449 frame_run: LabelFrame
1450 startup_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001451 (*)startup_editor_on: Radiobutton - startup_edit
1452 (*)startup_shell_on: Radiobutton - startup_edit
csabella36329a42017-07-13 23:32:01 -04001453 frame_save: LabelFrame
1454 run_save_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001455 (*)save_ask_on: Radiobutton - autosave
1456 (*)save_auto_on: Radiobutton - autosave
csabella36329a42017-07-13 23:32:01 -04001457 frame_win_size: LabelFrame
1458 win_size_title: Label
1459 win_width_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001460 (*)win_width_int: Entry - win_width
csabella36329a42017-07-13 23:32:01 -04001461 win_height_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001462 (*)win_height_int: Entry - win_height
csabella36329a42017-07-13 23:32:01 -04001463 frame_help: LabelFrame
1464 frame_helplist: Frame
1465 frame_helplist_buttons: Frame
1466 (*)button_helplist_edit
1467 (*)button_helplist_add
1468 (*)button_helplist_remove
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001469 (*)helplist: ListBox
csabella36329a42017-07-13 23:32:01 -04001470 scroll_helplist: Scrollbar
csabella7eb58832017-07-04 21:30:58 -04001471 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001472 parent = self.parent
csabellabac7d332017-06-26 17:46:26 -04001473 self.startup_edit = IntVar(parent)
1474 self.autosave = IntVar(parent)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001475 self.win_width = StringVar(parent)
1476 self.win_height = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001477
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001478 # Create widgets:
1479 # body.
csabellabac7d332017-06-26 17:46:26 -04001480 frame = self.tab_pages.pages['General'].frame
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001481 # body section frames.
csabellabac7d332017-06-26 17:46:26 -04001482 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001483 text=' Startup Preferences ')
csabellabac7d332017-06-26 17:46:26 -04001484 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
1485 text=' autosave Preferences ')
1486 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
1487 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001488 text=' Additional Help Sources ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001489 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001490 startup_title = Label(frame_run, text='At Startup')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001491 self.startup_editor_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001492 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001493 text="Open Edit Window")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001494 self.startup_shell_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001495 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001496 text='Open Shell Window')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001497 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001498 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001499 self.save_ask_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001500 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001501 text="Prompt to Save")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001502 self.save_auto_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001503 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001504 text='No Prompt')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001505 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001506 win_size_title = Label(
1507 frame_win_size, text='Initial Window Size (in characters)')
1508 win_width_title = Label(frame_win_size, text='Width')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001509 self.win_width_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001510 frame_win_size, textvariable=self.win_width, width=3)
1511 win_height_title = Label(frame_win_size, text='Height')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001512 self.win_height_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001513 frame_win_size, textvariable=self.win_height, width=3)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001514 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001515 frame_helplist = Frame(frame_help)
1516 frame_helplist_buttons = Frame(frame_helplist)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001517 self.helplist = Listbox(
csabellabac7d332017-06-26 17:46:26 -04001518 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001519 exportselection=FALSE)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001520 scroll_helplist = Scrollbar(frame_helplist)
1521 scroll_helplist['command'] = self.helplist.yview
1522 self.helplist['yscrollcommand'] = scroll_helplist.set
1523 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
csabellabac7d332017-06-26 17:46:26 -04001524 self.button_helplist_edit = Button(
1525 frame_helplist_buttons, text='Edit', state=DISABLED,
1526 width=8, command=self.helplist_item_edit)
1527 self.button_helplist_add = Button(
1528 frame_helplist_buttons, text='Add',
1529 width=8, command=self.helplist_item_add)
1530 self.button_helplist_remove = Button(
1531 frame_helplist_buttons, text='Remove', state=DISABLED,
1532 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001533
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001534 # Pack widgets:
1535 # body.
csabellabac7d332017-06-26 17:46:26 -04001536 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
1537 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
1538 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
1539 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001540 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001541 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001542 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1543 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1544 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001545 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001546 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1547 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1548 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001549 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001550 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001551 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001552 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001553 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001554 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001555 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
1556 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1557 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001558 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
csabellabac7d332017-06-26 17:46:26 -04001559 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
1560 self.button_helplist_add.pack(side=TOP, anchor=W)
1561 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001562
Steven M. Gava952d0a52001-08-03 04:43:44 +00001563 return frame
1564
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001565 def load_general_cfg(self):
1566 "Load current configuration settings for the general options."
1567 # Set startup state.
1568 self.startup_edit.set(idleConf.GetOption(
1569 'main', 'General', 'editor-on-startup', default=0, type='bool'))
1570 # Set autosave state.
1571 self.autosave.set(idleConf.GetOption(
1572 'main', 'General', 'autosave', default=0, type='bool'))
1573 # Set initial window size.
1574 self.win_width.set(idleConf.GetOption(
1575 'main', 'EditorWindow', 'width', type='int'))
1576 self.win_height.set(idleConf.GetOption(
1577 'main', 'EditorWindow', 'height', type='int'))
1578 # Set additional help sources.
1579 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1580 self.helplist.delete(0, 'end')
1581 for help_item in self.user_helplist:
1582 self.helplist.insert(END, help_item[0])
1583 self.set_add_delete_state()
1584
1585 def var_changed_startup_edit(self, *params):
1586 "Store change to toggle for starting IDLE in the editor or shell."
1587 value = self.startup_edit.get()
1588 changes.add_option('main', 'General', 'editor-on-startup', value)
1589
1590 def var_changed_autosave(self, *params):
1591 "Store change to autosave."
1592 value = self.autosave.get()
1593 changes.add_option('main', 'General', 'autosave', value)
1594
1595 def var_changed_win_width(self, *params):
1596 "Store change to window width."
1597 value = self.win_width.get()
1598 changes.add_option('main', 'EditorWindow', 'width', value)
1599
1600 def var_changed_win_height(self, *params):
1601 "Store change to window height."
1602 value = self.win_height.get()
1603 changes.add_option('main', 'EditorWindow', 'height', value)
1604
1605 def help_source_selected(self, event):
1606 "Handle event for selecting additional help."
1607 self.set_add_delete_state()
1608
1609 def set_add_delete_state(self):
1610 "Toggle the state for the help list buttons based on list entries."
1611 if self.helplist.size() < 1: # No entries in list.
1612 self.button_helplist_edit['state'] = DISABLED
1613 self.button_helplist_remove['state'] = DISABLED
1614 else: # Some entries.
1615 if self.helplist.curselection(): # There currently is a selection.
1616 self.button_helplist_edit['state'] = NORMAL
1617 self.button_helplist_remove['state'] = NORMAL
1618 else: # There currently is not a selection.
1619 self.button_helplist_edit['state'] = DISABLED
1620 self.button_helplist_remove['state'] = DISABLED
1621
1622 def helplist_item_add(self):
1623 """Handle add button for the help list.
1624
1625 Query for name and location of new help sources and add
1626 them to the list.
1627 """
1628 help_source = HelpSource(self, 'New Help Source').result
1629 if help_source:
1630 self.user_helplist.append(help_source)
1631 self.helplist.insert(END, help_source[0])
1632 self.update_help_changes()
1633
1634 def helplist_item_edit(self):
1635 """Handle edit button for the help list.
1636
1637 Query with existing help source information and update
1638 config if the values are changed.
1639 """
1640 item_index = self.helplist.index(ANCHOR)
1641 help_source = self.user_helplist[item_index]
1642 new_help_source = HelpSource(
1643 self, 'Edit Help Source',
1644 menuitem=help_source[0],
1645 filepath=help_source[1],
1646 ).result
1647 if new_help_source and new_help_source != help_source:
1648 self.user_helplist[item_index] = new_help_source
1649 self.helplist.delete(item_index)
1650 self.helplist.insert(item_index, new_help_source[0])
1651 self.update_help_changes()
1652 self.set_add_delete_state() # Selected will be un-selected
1653
1654 def helplist_item_remove(self):
1655 """Handle remove button for the help list.
1656
1657 Delete the help list item from config.
1658 """
1659 item_index = self.helplist.index(ANCHOR)
1660 del(self.user_helplist[item_index])
1661 self.helplist.delete(item_index)
1662 self.update_help_changes()
1663 self.set_add_delete_state()
1664
1665 def update_help_changes(self):
1666 "Clear and rebuild the HelpFiles section in changes"
1667 changes['main']['HelpFiles'] = {}
1668 for num in range(1, len(self.user_helplist) + 1):
1669 changes.add_option(
1670 'main', 'HelpFiles', str(num),
1671 ';'.join(self.user_helplist[num-1][:2]))
1672
1673
csabellabac7d332017-06-26 17:46:26 -04001674 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001675 """Part of the config dialog used for configuring IDLE extensions.
1676
1677 This code is generic - it works for any and all IDLE extensions.
1678
1679 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001680 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001681 GUI interface to change the configuration values, and saves the
1682 changes using idleConf.
1683
1684 Not all changes take effect immediately - some may require restarting IDLE.
1685 This depends on each extension's implementation.
1686
1687 All values are treated as text, and it is up to the user to supply
1688 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001689 which are boolean, and can be toggled with a True/False button.
csabella36329a42017-07-13 23:32:01 -04001690
1691 Methods:
1692 load_extentions:
1693 extension_selected: Handle selection from list.
1694 create_extension_frame: Hold widgets for one extension.
1695 set_extension_value: Set in userCfg['extensions'].
1696 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001697 """
1698 parent = self.parent
csabellabac7d332017-06-26 17:46:26 -04001699 frame = self.tab_pages.pages['Extensions'].frame
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001700 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1701 self.ext_userCfg = idleConf.userCfg['extensions']
1702 self.is_int = self.register(is_int)
1703 self.load_extensions()
csabella7eb58832017-07-04 21:30:58 -04001704 # Create widgets - a listbox shows all available extensions, with the
1705 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001706 self.extension_names = StringVar(self)
1707 frame.rowconfigure(0, weight=1)
1708 frame.columnconfigure(2, weight=1)
1709 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1710 selectmode='browse')
1711 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1712 scroll = Scrollbar(frame, command=self.extension_list.yview)
1713 self.extension_list.yscrollcommand=scroll.set
1714 self.details_frame = LabelFrame(frame, width=250, height=250)
1715 self.extension_list.grid(column=0, row=0, sticky='nws')
1716 scroll.grid(column=1, row=0, sticky='ns')
1717 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1718 frame.configure(padx=10, pady=10)
1719 self.config_frame = {}
1720 self.current_extension = None
1721
1722 self.outerframe = self # TEMPORARY
1723 self.tabbed_page_set = self.extension_list # TEMPORARY
1724
csabella7eb58832017-07-04 21:30:58 -04001725 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001726 ext_names = ''
1727 for ext_name in sorted(self.extensions):
1728 self.create_extension_frame(ext_name)
1729 ext_names = ext_names + '{' + ext_name + '} '
1730 self.extension_names.set(ext_names)
1731 self.extension_list.selection_set(0)
1732 self.extension_selected(None)
1733
1734 def load_extensions(self):
1735 "Fill self.extensions with data from the default and user configs."
1736 self.extensions = {}
1737 for ext_name in idleConf.GetExtensions(active_only=False):
1738 self.extensions[ext_name] = []
1739
1740 for ext_name in self.extensions:
1741 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1742
csabella7eb58832017-07-04 21:30:58 -04001743 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001744 enables = [opt_name for opt_name in opt_list
1745 if opt_name.startswith('enable')]
1746 for opt_name in enables:
1747 opt_list.remove(opt_name)
1748 opt_list = enables + opt_list
1749
1750 for opt_name in opt_list:
1751 def_str = self.ext_defaultCfg.Get(
1752 ext_name, opt_name, raw=True)
1753 try:
1754 def_obj = {'True':True, 'False':False}[def_str]
1755 opt_type = 'bool'
1756 except KeyError:
1757 try:
1758 def_obj = int(def_str)
1759 opt_type = 'int'
1760 except ValueError:
1761 def_obj = def_str
1762 opt_type = None
1763 try:
1764 value = self.ext_userCfg.Get(
1765 ext_name, opt_name, type=opt_type, raw=True,
1766 default=def_obj)
csabella7eb58832017-07-04 21:30:58 -04001767 except ValueError: # Need this until .Get fixed.
1768 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001769 var = StringVar(self)
1770 var.set(str(value))
1771
1772 self.extensions[ext_name].append({'name': opt_name,
1773 'type': opt_type,
1774 'default': def_str,
1775 'value': value,
1776 'var': var,
1777 })
1778
1779 def extension_selected(self, event):
csabella7eb58832017-07-04 21:30:58 -04001780 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001781 newsel = self.extension_list.curselection()
1782 if newsel:
1783 newsel = self.extension_list.get(newsel)
1784 if newsel is None or newsel != self.current_extension:
1785 if self.current_extension:
1786 self.details_frame.config(text='')
1787 self.config_frame[self.current_extension].grid_forget()
1788 self.current_extension = None
1789 if newsel:
1790 self.details_frame.config(text=newsel)
1791 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1792 self.current_extension = newsel
1793
1794 def create_extension_frame(self, ext_name):
1795 """Create a frame holding the widgets to configure one extension"""
1796 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1797 self.config_frame[ext_name] = f
1798 entry_area = f.interior
csabella7eb58832017-07-04 21:30:58 -04001799 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001800 for row, opt in enumerate(self.extensions[ext_name]):
csabella7eb58832017-07-04 21:30:58 -04001801 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001802 label = Label(entry_area, text=opt['name'])
1803 label.grid(row=row, column=0, sticky=NW)
1804 var = opt['var']
1805 if opt['type'] == 'bool':
1806 Checkbutton(entry_area, textvariable=var, variable=var,
1807 onvalue='True', offvalue='False',
1808 indicatoron=FALSE, selectcolor='', width=8
1809 ).grid(row=row, column=1, sticky=W, padx=7)
1810 elif opt['type'] == 'int':
1811 Entry(entry_area, textvariable=var, validate='key',
1812 validatecommand=(self.is_int, '%P')
1813 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1814
1815 else:
1816 Entry(entry_area, textvariable=var
1817 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1818 return
1819
1820 def set_extension_value(self, section, opt):
csabella7eb58832017-07-04 21:30:58 -04001821 """Return True if the configuration was added or changed.
1822
1823 If the value is the same as the default, then remove it
1824 from user config file.
1825 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001826 name = opt['name']
1827 default = opt['default']
1828 value = opt['var'].get().strip() or default
1829 opt['var'].set(value)
1830 # if self.defaultCfg.has_section(section):
csabella7eb58832017-07-04 21:30:58 -04001831 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001832 if (value == default):
1833 return self.ext_userCfg.RemoveOption(section, name)
csabella7eb58832017-07-04 21:30:58 -04001834 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001835 return self.ext_userCfg.SetOption(section, name, value)
1836
1837 def save_all_changed_extensions(self):
csabella36329a42017-07-13 23:32:01 -04001838 """Save configuration changes to the user config file.
1839
1840 Attributes accessed:
1841 extensions
1842
1843 Methods:
1844 set_extension_value
1845 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001846 has_changes = False
1847 for ext_name in self.extensions:
1848 options = self.extensions[ext_name]
1849 for opt in options:
1850 if self.set_extension_value(ext_name, opt):
1851 has_changes = True
1852 if has_changes:
1853 self.ext_userCfg.Save()
1854
1855
csabella45bf7232017-07-26 19:09:58 -04001856class VarTrace:
1857 """Maintain Tk variables trace state."""
1858
1859 def __init__(self):
1860 """Store Tk variables and callbacks.
1861
1862 untraced: List of tuples (var, callback)
1863 that do not have the callback attached
1864 to the Tk var.
1865 traced: List of tuples (var, callback) where
1866 that callback has been attached to the var.
1867 """
1868 self.untraced = []
1869 self.traced = []
1870
1871 def add(self, var, callback):
1872 """Add (var, callback) tuple to untraced list.
1873
1874 Args:
1875 var: Tk variable instance.
1876 callback: Function to be used as a callback or
1877 a tuple with IdleConf values for default
1878 callback.
1879
1880 Return:
1881 Tk variable instance.
1882 """
1883 if isinstance(callback, tuple):
1884 callback = self.make_callback(var, callback)
1885 self.untraced.append((var, callback))
1886 return var
1887
1888 @staticmethod
1889 def make_callback(var, config):
1890 "Return default callback function to add values to changes instance."
1891 def default_callback(*params):
1892 "Add config values to changes instance."
1893 changes.add_option(*config, var.get())
1894 return default_callback
1895
1896 def attach(self):
1897 "Attach callback to all vars that are not traced."
1898 while self.untraced:
1899 var, callback = self.untraced.pop()
1900 var.trace_add('write', callback)
1901 self.traced.append((var, callback))
1902
1903 def detach(self):
1904 "Remove callback from traced vars."
1905 while self.traced:
1906 var, callback = self.traced.pop()
1907 var.trace_remove('write', var.trace_info()[0][1])
1908 self.untraced.append((var, callback))
1909
1910
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001911help_common = '''\
1912When you click either the Apply or Ok buttons, settings in this
1913dialog that are different from IDLE's default are saved in
1914a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001915these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001916machine. Some do not take affect until IDLE is restarted.
1917[Cancel] only cancels changes made since the last save.
1918'''
1919help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001920 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001921Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001922The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001923be used with older IDLE releases if it is saved as a custom
1924theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001925''',
1926 'Keys': '''
1927Keys:
1928The IDLE Modern Unix key set is new in June 2016. It can only
1929be used with older IDLE releases if it is saved as a custom
1930key set, with a different name.
1931''',
wohlgangerfae2c352017-06-27 21:36:23 -05001932 'Extensions': '''
1933Extensions:
1934
1935Autocomplete: Popupwait is milleseconds to wait after key char, without
1936cursor movement, before popping up completion box. Key char is '.' after
1937identifier or a '/' (or '\\' on Windows) within a string.
1938
1939FormatParagraph: Max-width is max chars in lines after re-formatting.
1940Use with paragraphs in both strings and comment blocks.
1941
1942ParenMatch: Style indicates what is highlighted when closer is entered:
1943'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1944'expression' (default) - also everything in between. Flash-delay is how
1945long to highlight if cursor is not moved (0 means forever).
1946'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001947}
1948
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001949
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001950def is_int(s):
1951 "Return 's is blank or represents an int'"
1952 if not s:
1953 return True
1954 try:
1955 int(s)
1956 return True
1957 except ValueError:
1958 return False
1959
1960
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001961class VerticalScrolledFrame(Frame):
1962 """A pure Tkinter vertically scrollable frame.
1963
1964 * Use the 'interior' attribute to place widgets inside the scrollable frame
1965 * Construct and pack/place/grid normally
1966 * This frame only allows vertical scrolling
1967 """
1968 def __init__(self, parent, *args, **kw):
1969 Frame.__init__(self, parent, *args, **kw)
1970
csabella7eb58832017-07-04 21:30:58 -04001971 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001972 vscrollbar = Scrollbar(self, orient=VERTICAL)
1973 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1974 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001975 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001976 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1977 vscrollbar.config(command=canvas.yview)
1978
csabella7eb58832017-07-04 21:30:58 -04001979 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001980 canvas.xview_moveto(0)
1981 canvas.yview_moveto(0)
1982
csabella7eb58832017-07-04 21:30:58 -04001983 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001984 self.interior = interior = Frame(canvas)
1985 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1986
csabella7eb58832017-07-04 21:30:58 -04001987 # Track changes to the canvas and frame width and sync them,
1988 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001989 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04001990 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001991 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1992 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001993 interior.bind('<Configure>', _configure_interior)
1994
1995 def _configure_canvas(event):
1996 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04001997 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001998 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1999 canvas.bind('<Configure>', _configure_canvas)
2000
2001 return
2002
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002003
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00002004if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04002005 import unittest
2006 unittest.main('idlelib.idle_test.test_configdialog',
2007 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04002008 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04002009 run(ConfigDialog)