blob: 92155e3726c16a293e8936b7ba6fc65af338166c [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
csabella5b591542017-07-28 14:40:59 -040033
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000034class ConfigDialog(Toplevel):
csabella7eb58832017-07-04 21:30:58 -040035 """Config dialog for IDLE.
36 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000037
Terry Jan Reedycd567362014-10-17 01:31:35 -040038 def __init__(self, parent, title='', _htest=False, _utest=False):
csabella7eb58832017-07-04 21:30:58 -040039 """Show the tabbed dialog for user configuration.
40
csabella36329a42017-07-13 23:32:01 -040041 Args:
42 parent - parent of this dialog
43 title - string which is the title of this popup dialog
44 _htest - bool, change box location when running htest
45 _utest - bool, don't wait_window when running unittest
46
47 Note: Focus set on font page fontlist.
48
49 Methods:
50 create_widgets
51 cancel: Bound to DELETE_WINDOW protocol.
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040052 """
Steven M. Gavad721c482001-07-31 10:46:53 +000053 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040054 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040055 if _htest:
56 parent.instance_dict = {}
Louie Lu9b622fb2017-07-14 08:35:48 +080057 if not _utest:
58 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000059
Steven M. Gavad721c482001-07-31 10:46:53 +000060 self.configure(borderwidth=5)
Terry Jan Reedycd567362014-10-17 01:31:35 -040061 self.title(title or 'IDLE Preferences')
csabellabac7d332017-06-26 17:46:26 -040062 x = parent.winfo_rootx() + 20
63 y = parent.winfo_rooty() + (30 if not _htest else 150)
64 self.geometry(f'+{x}+{y}')
csabella7eb58832017-07-04 21:30:58 -040065 # Each theme element key is its display name.
66 # The first value of the tuple is the sample area tag name.
67 # The second value is the display name list sort index.
csabellabac7d332017-06-26 17:46:26 -040068 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040069 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000070 self.transient(parent)
csabellabac7d332017-06-26 17:46:26 -040071 self.protocol("WM_DELETE_WINDOW", self.cancel)
Louie Lubb2bae82017-07-10 06:57:18 +080072 self.fontlist.focus_set()
csabella7eb58832017-07-04 21:30:58 -040073 # XXX Decide whether to keep or delete these key bindings.
74 # Key bindings for this dialog.
75 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
76 # self.bind('<Alt-a>', self.Apply) #apply changes, save
77 # self.bind('<F1>', self.Help) #context help
csabellabac7d332017-06-26 17:46:26 -040078 self.load_configs()
csabella5b591542017-07-28 14:40:59 -040079 # Avoid callbacks during load_configs.
80 tracers.attach()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000081
Terry Jan Reedycfa89502014-07-14 23:07:32 -040082 if not _utest:
Louie Lu9b622fb2017-07-14 08:35:48 +080083 self.grab_set()
Terry Jan Reedycfa89502014-07-14 23:07:32 -040084 self.wm_deiconify()
85 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000086
csabella5b591542017-07-28 14:40:59 -040087
csabellabac7d332017-06-26 17:46:26 -040088 def create_widgets(self):
csabella36329a42017-07-13 23:32:01 -040089 """Create and place widgets for tabbed dialog.
90
91 Widgets Bound to self:
92 tab_pages: TabbedPageSet
93
94 Methods:
95 create_page_font_tab
96 create_page_highlight
97 create_page_keys
98 create_page_general
99 create_page_extensions
100 create_action_buttons
101 load_configs: Load pages except for extensions.
csabella36329a42017-07-13 23:32:01 -0400102 remove_var_callbacks
103 activate_config_changes: Tell editors to reload.
104 """
csabellabac7d332017-06-26 17:46:26 -0400105 self.tab_pages = TabbedPageSet(self,
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400106 page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
107 'Extensions'])
csabellabac7d332017-06-26 17:46:26 -0400108 self.tab_pages.pack(side=TOP, expand=TRUE, fill=BOTH)
109 self.create_page_font_tab()
110 self.create_page_highlight()
111 self.create_page_keys()
112 self.create_page_general()
113 self.create_page_extensions()
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400114 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400115
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400116 def load_configs(self):
117 """Load configuration for each page.
118
119 Load configuration from default and user config files and populate
120 the widgets on the config dialog pages.
121
122 Methods:
123 load_font_cfg
124 load_tab_cfg
125 load_theme_cfg
126 load_key_cfg
127 load_general_cfg
128 """
129 self.load_font_cfg()
130 self.load_tab_cfg()
131 self.load_theme_cfg()
132 self.load_key_cfg()
133 self.load_general_cfg()
134 # note: extension page handled separately
135
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400136 def remove_var_callbacks(self):
137 "Remove callbacks to prevent memory leaks."
csabella5b591542017-07-28 14:40:59 -0400138 tracers.detach()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400139
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400140 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400141 """Return frame of action buttons for dialog.
142
143 Methods:
144 ok
145 apply
146 cancel
147 help
148
149 Widget Structure:
150 outer: Frame
151 buttons: Frame
152 (no assignment): Button (ok)
153 (no assignment): Button (apply)
154 (no assignment): Button (cancel)
155 (no assignment): Button (help)
156 (no assignment): Frame
157 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400158 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400159 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400160 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400161 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000162 else:
csabellabac7d332017-06-26 17:46:26 -0400163 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400164 outer = Frame(self, pady=2)
165 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400166 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400167 ('Ok', self.ok),
168 ('Apply', self.apply),
169 ('Cancel', self.cancel),
170 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400171 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
csabellabac7d332017-06-26 17:46:26 -0400172 **padding_args).pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400173 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400174 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
175 buttons.pack(side=BOTTOM)
176 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400177
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400178 def ok(self):
179 """Apply config changes, then dismiss dialog.
180
181 Methods:
182 apply
183 destroy: inherited
184 """
185 self.apply()
186 self.destroy()
187
188 def apply(self):
189 """Apply config changes and leave dialog open.
190
191 Methods:
192 deactivate_current_config
193 save_all_changed_extensions
194 activate_config_changes
195 """
196 self.deactivate_current_config()
197 changes.save_all()
198 self.save_all_changed_extensions()
199 self.activate_config_changes()
200
201 def cancel(self):
202 """Dismiss config dialog.
203
204 Methods:
205 destroy: inherited
206 """
207 self.destroy()
208
209 def help(self):
210 """Create textview for config dialog help.
211
212 Attrbutes accessed:
213 tab_pages
214
215 Methods:
216 view_text: Method from textview module.
217 """
218 page = self.tab_pages._current_page
219 view_text(self, title='Help for IDLE preferences',
220 text=help_common+help_pages.get(page, ''))
221
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400222
csabellabac7d332017-06-26 17:46:26 -0400223 def create_page_font_tab(self):
csabella7eb58832017-07-04 21:30:58 -0400224 """Return frame of widgets for Font/Tabs tab.
225
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400226 Fonts: Enable users to provisionally change font face, size, or
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400227 boldness and to see the consequence of proposed choices. Each
228 action set 3 options in changes structuree and changes the
229 corresponding aspect of the font sample on this page and
230 highlight sample on highlight page.
231
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400232 Funtion load_font_cfg initializes font vars and widgets from
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400233 idleConf entries and tk.
234
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400235 Fontlist: mouse button 1 click or up or down key invoke
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400236 on_fontlist_select(), which sets var font_name.
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400237
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400238 Sizelist: clicking the menubutton opens the dropdown menu. A
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400239 mouse button 1 click or return key sets var font_size.
csabella36329a42017-07-13 23:32:01 -0400240
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400241 Bold_toggle: clicking the box toggles var font_bold.
csabella36329a42017-07-13 23:32:01 -0400242
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400243 Changing any of the font vars invokes var_changed_font, which
244 adds all 3 font options to changes and calls set_samples.
245 Set_samples applies a new font constructed from the font vars to
246 font_sample and to highlight_sample on the hightlight page.
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400247
248 Tabs: Enable users to change spaces entered for indent tabs.
249 Changing indent_scale value with the mouse sets Var space_num,
csabella5b591542017-07-28 14:40:59 -0400250 which invokes the default callback to add an entry to
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400251 changes. Load_tab_cfg initializes space_num to default.
csabella36329a42017-07-13 23:32:01 -0400252
253 Widget Structure: (*) widgets bound to self
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400254 frame (of tab_pages)
csabella36329a42017-07-13 23:32:01 -0400255 frame_font: LabelFrame
256 frame_font_name: Frame
257 font_name_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400258 (*)fontlist: ListBox - font_name
csabella36329a42017-07-13 23:32:01 -0400259 scroll_font: Scrollbar
260 frame_font_param: Frame
261 font_size_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400262 (*)sizelist: DynOptionMenu - font_size
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400263 (*)bold_toggle: Checkbutton - font_bold
csabella36329a42017-07-13 23:32:01 -0400264 frame_font_sample: Frame
265 (*)font_sample: Label
266 frame_indent: LabelFrame
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400267 indent_title: Label
268 (*)indent_scale: Scale - space_num
csabella7eb58832017-07-04 21:30:58 -0400269 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400270 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400271 self.font_name = tracers.add(StringVar(parent), self.var_changed_font)
272 self.font_size = tracers.add(StringVar(parent), self.var_changed_font)
273 self.font_bold = tracers.add(BooleanVar(parent), self.var_changed_font)
274 self.space_num = tracers.add(IntVar(parent), ('main', 'Indent', 'num-spaces'))
Terry Jan Reedy22405332014-07-30 19:24:32 -0400275
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400276 # Create widgets:
Louie Lubb2bae82017-07-10 06:57:18 +0800277 # body and body section frames.
csabellabac7d332017-06-26 17:46:26 -0400278 frame = self.tab_pages.pages['Fonts/Tabs'].frame
csabellabac7d332017-06-26 17:46:26 -0400279 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400280 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
csabellabac7d332017-06-26 17:46:26 -0400281 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400282 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400283 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400284 frame_font_name = Frame(frame_font)
285 frame_font_param = Frame(frame_font)
286 font_name_title = Label(
287 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400288 self.fontlist = Listbox(frame_font_name, height=5,
289 takefocus=FALSE, exportselection=FALSE)
terryjreedy5b62b352017-07-11 01:58:04 -0400290 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
291 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
292 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
csabellabac7d332017-06-26 17:46:26 -0400293 scroll_font = Scrollbar(frame_font_name)
Louie Lubb2bae82017-07-10 06:57:18 +0800294 scroll_font.config(command=self.fontlist.yview)
295 self.fontlist.config(yscrollcommand=scroll_font.set)
csabellabac7d332017-06-26 17:46:26 -0400296 font_size_title = Label(frame_font_param, text='Size :')
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400297 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400298 self.bold_toggle = Checkbutton(
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400299 frame_font_param, variable=self.font_bold,
300 onvalue=1, offvalue=0, text='Bold')
csabellabac7d332017-06-26 17:46:26 -0400301 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400302 temp_font = tkFont.Font(parent, ('courier', 10, 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400303 self.font_sample = Label(
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400304 frame_font_sample, justify=LEFT, font=temp_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400305 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400306 # frame_indent.
307 indent_title = Label(
308 frame_indent, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400309 text='Python Standard: 4 Spaces!')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400310 self.indent_scale = Scale(
311 frame_indent, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400312 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400313
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400314 # Pack widgets:
315 # body.
csabellabac7d332017-06-26 17:46:26 -0400316 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
317 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400318 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400319 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
320 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
321 font_name_title.pack(side=TOP, anchor=W)
Louie Lubb2bae82017-07-10 06:57:18 +0800322 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400323 scroll_font.pack(side=LEFT, fill=Y)
324 font_size_title.pack(side=LEFT, anchor=W)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400325 self.sizelist.pack(side=LEFT, anchor=W)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400326 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
csabellabac7d332017-06-26 17:46:26 -0400327 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
328 self.font_sample.pack(expand=TRUE, fill=BOTH)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400329 # frame_indent.
330 frame_indent.pack(side=TOP, fill=X)
331 indent_title.pack(side=TOP, anchor=W, padx=5)
332 self.indent_scale.pack(side=TOP, padx=5, fill=X)
Louie Lubb2bae82017-07-10 06:57:18 +0800333
Steven M. Gava952d0a52001-08-03 04:43:44 +0000334 return frame
335
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400336 def load_font_cfg(self):
337 """Load current configuration settings for the font options.
338
339 Retrieve current font with idleConf.GetFont and font families
340 from tk. Setup fontlist and set font_name. Setup sizelist,
341 which sets font_size. Set font_bold. Setting font variables
342 calls set_samples (thrice).
343 """
344 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
345 font_name = configured_font[0].lower()
346 font_size = configured_font[1]
347 font_bold = configured_font[2]=='bold'
348
349 # Set editor font selection list and font_name.
350 fonts = list(tkFont.families(self))
351 fonts.sort()
352 for font in fonts:
353 self.fontlist.insert(END, font)
354 self.font_name.set(font_name)
355 lc_fonts = [s.lower() for s in fonts]
356 try:
357 current_font_index = lc_fonts.index(font_name)
358 self.fontlist.see(current_font_index)
359 self.fontlist.select_set(current_font_index)
360 self.fontlist.select_anchor(current_font_index)
361 self.fontlist.activate(current_font_index)
362 except ValueError:
363 pass
364 # Set font size dropdown.
365 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
366 '16', '18', '20', '22', '25', '29', '34', '40'),
367 font_size)
368 # Set font weight.
369 self.font_bold.set(font_bold)
370
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400371 def var_changed_font(self, *params):
372 """Store changes to font attributes.
373
374 When one font attribute changes, save them all, as they are
375 not independent from each other. In particular, when we are
376 overriding the default font, we need to write out everything.
377 """
378 value = self.font_name.get()
379 changes.add_option('main', 'EditorWindow', 'font', value)
380 value = self.font_size.get()
381 changes.add_option('main', 'EditorWindow', 'font-size', value)
382 value = self.font_bold.get()
383 changes.add_option('main', 'EditorWindow', 'font-bold', value)
384 self.set_samples()
385
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400386 def on_fontlist_select(self, event):
387 """Handle selecting a font from the list.
388
389 Event can result from either mouse click or Up or Down key.
390 Set font_name and example displays to selection.
391 """
392 font = self.fontlist.get(
393 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
394 self.font_name.set(font.lower())
395
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400396 def set_samples(self, event=None):
397 """Update update both screen samples with the font settings.
398
399 Called on font initialization and change events.
400 Accesses font_name, font_size, and font_bold Variables.
401 Updates font_sample and hightlight page highlight_sample.
402 """
403 font_name = self.font_name.get()
404 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
405 new_font = (font_name, self.font_size.get(), font_weight)
406 self.font_sample['font'] = new_font
407 self.highlight_sample['font'] = new_font
408
409 def load_tab_cfg(self):
410 """Load current configuration settings for the tab options.
411
412 Attributes updated:
413 space_num: Set to value from idleConf.
414 """
415 # Set indent sizes.
416 space_num = idleConf.GetOption(
417 'main', 'Indent', 'num-spaces', default=4, type='int')
418 self.space_num.set(space_num)
419
csabellabac7d332017-06-26 17:46:26 -0400420 def create_page_highlight(self):
csabella7eb58832017-07-04 21:30:58 -0400421 """Return frame of widgets for Highlighting tab.
422
csabella36329a42017-07-13 23:32:01 -0400423 Tk Variables:
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400424 color: Color of selected target.
csabella7eb58832017-07-04 21:30:58 -0400425 builtin_theme: Menu variable for built-in theme.
426 custom_theme: Menu variable for custom theme.
427 fg_bg_toggle: Toggle for foreground/background color.
csabella36329a42017-07-13 23:32:01 -0400428 Note: this has no callback.
csabella7eb58832017-07-04 21:30:58 -0400429 is_builtin_theme: Selector for built-in or custom theme.
430 highlight_target: Menu variable for the highlight tag target.
csabella36329a42017-07-13 23:32:01 -0400431
432 Instance Data Attributes:
433 theme_elements: Dictionary of tags for text highlighting.
434 The key is the display name and the value is a tuple of
435 (tag name, display sort order).
436
437 Methods [attachment]:
438 load_theme_cfg: Load current highlight colors.
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400439 get_color: Invoke colorchooser [button_set_color].
440 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
csabella36329a42017-07-13 23:32:01 -0400441 set_highlight_target: set fg_bg_toggle, set_color_sample().
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400442 set_color_sample: Set frame background to target.
443 on_new_color_set: Set new color and add option.
csabella36329a42017-07-13 23:32:01 -0400444 paint_theme_sample: Recolor sample.
445 get_new_theme_name: Get from popup.
446 create_new_theme: Combine theme with changes and save.
447 save_as_new_theme: Save [button_save_custom_theme].
448 set_theme_type: Command for [is_builtin_theme].
449 delete_custom_theme: Ativate default [button_delete_custom_theme].
450 save_new_theme: Save to userCfg['theme'] (is function).
451
452 Widget Structure: (*) widgets bound to self
453 frame
454 frame_custom: LabelFrame
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400455 (*)highlight_sample: Text
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400456 (*)frame_color_set: Frame
457 button_set_color: Button
csabella36329a42017-07-13 23:32:01 -0400458 (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
459 frame_fg_bg_toggle: Frame
460 (*)radio_fg: Radiobutton - fg_bg_toggle
461 (*)radio_bg: Radiobutton - fg_bg_toggle
462 button_save_custom_theme: Button
463 frame_theme: LabelFrame
464 theme_type_title: Label
465 (*)radio_theme_builtin: Radiobutton - is_builtin_theme
466 (*)radio_theme_custom: Radiobutton - is_builtin_theme
467 (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
468 (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
469 (*)button_delete_custom_theme: Button
470 (*)new_custom_theme: Label
csabella7eb58832017-07-04 21:30:58 -0400471 """
csabella36329a42017-07-13 23:32:01 -0400472 self.theme_elements={
473 'Normal Text': ('normal', '00'),
474 'Python Keywords': ('keyword', '01'),
475 'Python Definitions': ('definition', '02'),
476 'Python Builtins': ('builtin', '03'),
477 'Python Comments': ('comment', '04'),
478 'Python Strings': ('string', '05'),
479 'Selected Text': ('hilite', '06'),
480 'Found Text': ('hit', '07'),
481 'Cursor': ('cursor', '08'),
482 'Editor Breakpoint': ('break', '09'),
483 'Shell Normal Text': ('console', '10'),
484 'Shell Error Text': ('error', '11'),
485 'Shell Stdout Text': ('stdout', '12'),
486 'Shell Stderr Text': ('stderr', '13'),
487 }
Terry Jan Reedy22405332014-07-30 19:24:32 -0400488 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400489 self.builtin_theme = tracers.add(
490 StringVar(parent), self.var_changed_builtin_theme)
491 self.custom_theme = tracers.add(
492 StringVar(parent), self.var_changed_custom_theme)
csabellabac7d332017-06-26 17:46:26 -0400493 self.fg_bg_toggle = BooleanVar(parent)
csabella5b591542017-07-28 14:40:59 -0400494 self.color = tracers.add(
495 StringVar(parent), self.var_changed_color)
496 self.is_builtin_theme = tracers.add(
497 BooleanVar(parent), self.var_changed_is_builtin_theme)
498 self.highlight_target = tracers.add(
499 StringVar(parent), self.var_changed_highlight_target)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400500
Steven M. Gava952d0a52001-08-03 04:43:44 +0000501 ##widget creation
502 #body frame
csabellabac7d332017-06-26 17:46:26 -0400503 frame = self.tab_pages.pages['Highlighting'].frame
Steven M. Gava952d0a52001-08-03 04:43:44 +0000504 #body section frames
csabellabac7d332017-06-26 17:46:26 -0400505 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400506 text=' Custom Highlighting ')
csabellabac7d332017-06-26 17:46:26 -0400507 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400508 text=' Highlighting Theme ')
csabellabac7d332017-06-26 17:46:26 -0400509 #frame_custom
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400510 self.highlight_sample=Text(
csabellabac7d332017-06-26 17:46:26 -0400511 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400512 font=('courier', 12, ''), cursor='hand2', width=21, height=11,
513 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400514 text=self.highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400515 text.bind('<Double-Button-1>', lambda e: 'break')
516 text.bind('<B1-Motion>', lambda e: 'break')
csabellabac7d332017-06-26 17:46:26 -0400517 text_and_tags=(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400518 ('#you can click here', 'comment'), ('\n', 'normal'),
519 ('#to choose items', 'comment'), ('\n', 'normal'),
520 ('def', 'keyword'), (' ', 'normal'),
521 ('func', 'definition'), ('(param):\n ', 'normal'),
522 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
523 ("'string'", 'string'), ('\n var1 = ', 'normal'),
524 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
525 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
526 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400527 ('None', 'keyword'), (')\n', 'normal'),
528 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400529 (' error ', 'error'), (' ', 'normal'),
530 ('cursor |', 'cursor'), ('\n ', 'normal'),
531 ('shell', 'console'), (' ', 'normal'),
532 ('stdout', 'stdout'), (' ', 'normal'),
533 ('stderr', 'stderr'), ('\n', 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400534 for texttag in text_and_tags:
535 text.insert(END, texttag[0], texttag[1])
536 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400537 def tem(event, elem=element):
csabellabac7d332017-06-26 17:46:26 -0400538 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400539 text.tag_bind(
csabellabac7d332017-06-26 17:46:26 -0400540 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400541 text['state'] = DISABLED
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400542 self.frame_color_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
csabellabac7d332017-06-26 17:46:26 -0400543 frame_fg_bg_toggle = Frame(frame_custom)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400544 button_set_color = Button(
545 self.frame_color_set, text='Choose Color for :',
546 command=self.get_color, highlightthickness=0)
csabellabac7d332017-06-26 17:46:26 -0400547 self.opt_menu_highlight_target = DynOptionMenu(
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400548 self.frame_color_set, self.highlight_target, None,
csabellabac7d332017-06-26 17:46:26 -0400549 highlightthickness=0) #, command=self.set_highlight_targetBinding
550 self.radio_fg = Radiobutton(
551 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400552 text='Foreground', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400553 self.radio_bg=Radiobutton(
554 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400555 text='Background', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400556 self.fg_bg_toggle.set(1)
557 button_save_custom_theme = Button(
558 frame_custom, text='Save as New Custom Theme',
559 command=self.save_as_new_theme)
560 #frame_theme
561 theme_type_title = Label(frame_theme, text='Select : ')
562 self.radio_theme_builtin = Radiobutton(
563 frame_theme, variable=self.is_builtin_theme, value=1,
564 command=self.set_theme_type, text='a Built-in Theme')
565 self.radio_theme_custom = Radiobutton(
566 frame_theme, variable=self.is_builtin_theme, value=0,
567 command=self.set_theme_type, text='a Custom Theme')
568 self.opt_menu_theme_builtin = DynOptionMenu(
569 frame_theme, self.builtin_theme, None, command=None)
570 self.opt_menu_theme_custom=DynOptionMenu(
571 frame_theme, self.custom_theme, None, command=None)
572 self.button_delete_custom_theme=Button(
573 frame_theme, text='Delete Custom Theme',
574 command=self.delete_custom_theme)
575 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400576
Steven M. Gava952d0a52001-08-03 04:43:44 +0000577 ##widget packing
578 #body
csabellabac7d332017-06-26 17:46:26 -0400579 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
580 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
581 #frame_custom
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400582 self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400583 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400584 self.highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400585 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400586 button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
csabellabac7d332017-06-26 17:46:26 -0400587 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400588 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
csabellabac7d332017-06-26 17:46:26 -0400589 self.radio_fg.pack(side=LEFT, anchor=E)
590 self.radio_bg.pack(side=RIGHT, anchor=W)
591 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
592 #frame_theme
593 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
594 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
595 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
596 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
597 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
598 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500599 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000600 return frame
601
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400602 def load_theme_cfg(self):
603 """Load current configuration settings for the theme options.
604
605 Based on the is_builtin_theme toggle, the theme is set as
606 either builtin or custom and the initial widget values
607 reflect the current settings from idleConf.
608
609 Attributes updated:
610 is_builtin_theme: Set from idleConf.
611 opt_menu_theme_builtin: List of default themes from idleConf.
612 opt_menu_theme_custom: List of custom themes from idleConf.
613 radio_theme_custom: Disabled if there are no custom themes.
614 custom_theme: Message with additional information.
615 opt_menu_highlight_target: Create menu from self.theme_elements.
616
617 Methods:
618 set_theme_type
619 paint_theme_sample
620 set_highlight_target
621 """
622 # Set current theme type radiobutton.
623 self.is_builtin_theme.set(idleConf.GetOption(
624 'main', 'Theme', 'default', type='bool', default=1))
625 # Set current theme.
626 current_option = idleConf.CurrentTheme()
627 # Load available theme option menus.
628 if self.is_builtin_theme.get(): # Default theme selected.
629 item_list = idleConf.GetSectionList('default', 'highlight')
630 item_list.sort()
631 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
632 item_list = idleConf.GetSectionList('user', 'highlight')
633 item_list.sort()
634 if not item_list:
635 self.radio_theme_custom['state'] = DISABLED
636 self.custom_theme.set('- no custom themes -')
637 else:
638 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
639 else: # User theme selected.
640 item_list = idleConf.GetSectionList('user', 'highlight')
641 item_list.sort()
642 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
643 item_list = idleConf.GetSectionList('default', 'highlight')
644 item_list.sort()
645 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
646 self.set_theme_type()
647 # Load theme element option menu.
648 theme_names = list(self.theme_elements.keys())
649 theme_names.sort(key=lambda x: self.theme_elements[x][1])
650 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
651 self.paint_theme_sample()
652 self.set_highlight_target()
653
654 def var_changed_builtin_theme(self, *params):
655 """Process new builtin theme selection.
656
657 Add the changed theme's name to the changed_items and recreate
658 the sample with the values from the selected theme.
659 """
660 old_themes = ('IDLE Classic', 'IDLE New')
661 value = self.builtin_theme.get()
662 if value not in old_themes:
663 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
664 changes.add_option('main', 'Theme', 'name', old_themes[0])
665 changes.add_option('main', 'Theme', 'name2', value)
666 self.new_custom_theme.config(text='New theme, see Help',
667 fg='#500000')
668 else:
669 changes.add_option('main', 'Theme', 'name', value)
670 changes.add_option('main', 'Theme', 'name2', '')
671 self.new_custom_theme.config(text='', fg='black')
672 self.paint_theme_sample()
673
674 def var_changed_custom_theme(self, *params):
675 """Process new custom theme selection.
676
677 If a new custom theme is selected, add the name to the
678 changed_items and apply the theme to the sample.
679 """
680 value = self.custom_theme.get()
681 if value != '- no custom themes -':
682 changes.add_option('main', 'Theme', 'name', value)
683 self.paint_theme_sample()
684
685 def var_changed_is_builtin_theme(self, *params):
686 """Process toggle between builtin and custom theme.
687
688 Update the default toggle value and apply the newly
689 selected theme type.
690 """
691 value = self.is_builtin_theme.get()
692 changes.add_option('main', 'Theme', 'default', value)
693 if value:
694 self.var_changed_builtin_theme()
695 else:
696 self.var_changed_custom_theme()
697
698 def var_changed_color(self, *params):
699 "Process change to color choice."
700 self.on_new_color_set()
701
702 def var_changed_highlight_target(self, *params):
703 "Process selection of new target tag for highlighting."
704 self.set_highlight_target()
705
706 def set_theme_type(self):
707 """Set available screen options based on builtin or custom theme.
708
709 Attributes accessed:
710 is_builtin_theme
711
712 Attributes updated:
713 opt_menu_theme_builtin
714 opt_menu_theme_custom
715 button_delete_custom_theme
716 radio_theme_custom
717
718 Called from:
719 handler for radio_theme_builtin and radio_theme_custom
720 delete_custom_theme
721 create_new_theme
722 load_theme_cfg
723 """
724 if self.is_builtin_theme.get():
725 self.opt_menu_theme_builtin['state'] = NORMAL
726 self.opt_menu_theme_custom['state'] = DISABLED
727 self.button_delete_custom_theme['state'] = DISABLED
728 else:
729 self.opt_menu_theme_builtin['state'] = DISABLED
730 self.radio_theme_custom['state'] = NORMAL
731 self.opt_menu_theme_custom['state'] = NORMAL
732 self.button_delete_custom_theme['state'] = NORMAL
733
734 def get_color(self):
735 """Handle button to select a new color for the target tag.
736
737 If a new color is selected while using a builtin theme, a
738 name must be supplied to create a custom theme.
739
740 Attributes accessed:
741 highlight_target
742 frame_color_set
743 is_builtin_theme
744
745 Attributes updated:
746 color
747
748 Methods:
749 get_new_theme_name
750 create_new_theme
751 """
752 target = self.highlight_target.get()
753 prev_color = self.frame_color_set.cget('bg')
754 rgbTuplet, color_string = tkColorChooser.askcolor(
755 parent=self, title='Pick new color for : '+target,
756 initialcolor=prev_color)
757 if color_string and (color_string != prev_color):
758 # User didn't cancel and they chose a new color.
759 if self.is_builtin_theme.get(): # Current theme is a built-in.
760 message = ('Your changes will be saved as a new Custom Theme. '
761 'Enter a name for your new Custom Theme below.')
762 new_theme = self.get_new_theme_name(message)
763 if not new_theme: # User cancelled custom theme creation.
764 return
765 else: # Create new custom theme based on previously active theme.
766 self.create_new_theme(new_theme)
767 self.color.set(color_string)
768 else: # Current theme is user defined.
769 self.color.set(color_string)
770
771 def on_new_color_set(self):
772 "Display sample of new color selection on the dialog."
773 new_color=self.color.get()
774 self.frame_color_set.config(bg=new_color) # Set sample.
775 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
776 sample_element = self.theme_elements[self.highlight_target.get()][0]
777 self.highlight_sample.tag_config(sample_element, **{plane:new_color})
778 theme = self.custom_theme.get()
779 theme_element = sample_element + '-' + plane
780 changes.add_option('highlight', theme, theme_element, new_color)
781
782 def get_new_theme_name(self, message):
783 "Return name of new theme from query popup."
784 used_names = (idleConf.GetSectionList('user', 'highlight') +
785 idleConf.GetSectionList('default', 'highlight'))
786 new_theme = SectionName(
787 self, 'New Custom Theme', message, used_names).result
788 return new_theme
789
790 def save_as_new_theme(self):
791 """Prompt for new theme name and create the theme.
792
793 Methods:
794 get_new_theme_name
795 create_new_theme
796 """
797 new_theme_name = self.get_new_theme_name('New Theme Name:')
798 if new_theme_name:
799 self.create_new_theme(new_theme_name)
800
801 def create_new_theme(self, new_theme_name):
802 """Create a new custom theme with the given name.
803
804 Create the new theme based on the previously active theme
805 with the current changes applied. Once it is saved, then
806 activate the new theme.
807
808 Attributes accessed:
809 builtin_theme
810 custom_theme
811
812 Attributes updated:
813 opt_menu_theme_custom
814 is_builtin_theme
815
816 Method:
817 save_new_theme
818 set_theme_type
819 """
820 if self.is_builtin_theme.get():
821 theme_type = 'default'
822 theme_name = self.builtin_theme.get()
823 else:
824 theme_type = 'user'
825 theme_name = self.custom_theme.get()
826 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
827 # Apply any of the old theme's unsaved changes to the new theme.
828 if theme_name in changes['highlight']:
829 theme_changes = changes['highlight'][theme_name]
830 for element in theme_changes:
831 new_theme[element] = theme_changes[element]
832 # Save the new theme.
833 self.save_new_theme(new_theme_name, new_theme)
834 # Change GUI over to the new theme.
835 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
836 custom_theme_list.sort()
837 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
838 self.is_builtin_theme.set(0)
839 self.set_theme_type()
840
841 def set_highlight_target(self):
842 """Set fg/bg toggle and color based on highlight tag target.
843
844 Instance variables accessed:
845 highlight_target
846
847 Attributes updated:
848 radio_fg
849 radio_bg
850 fg_bg_toggle
851
852 Methods:
853 set_color_sample
854
855 Called from:
856 var_changed_highlight_target
857 load_theme_cfg
858 """
859 if self.highlight_target.get() == 'Cursor': # bg not possible
860 self.radio_fg['state'] = DISABLED
861 self.radio_bg['state'] = DISABLED
862 self.fg_bg_toggle.set(1)
863 else: # Both fg and bg can be set.
864 self.radio_fg['state'] = NORMAL
865 self.radio_bg['state'] = NORMAL
866 self.fg_bg_toggle.set(1)
867 self.set_color_sample()
868
869 def set_color_sample_binding(self, *args):
870 """Change color sample based on foreground/background toggle.
871
872 Methods:
873 set_color_sample
874 """
875 self.set_color_sample()
876
877 def set_color_sample(self):
878 """Set the color of the frame background to reflect the selected target.
879
880 Instance variables accessed:
881 theme_elements
882 highlight_target
883 fg_bg_toggle
884 highlight_sample
885
886 Attributes updated:
887 frame_color_set
888 """
889 # Set the color sample area.
890 tag = self.theme_elements[self.highlight_target.get()][0]
891 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
892 color = self.highlight_sample.tag_cget(tag, plane)
893 self.frame_color_set.config(bg=color)
894
895 def paint_theme_sample(self):
896 """Apply the theme colors to each element tag in the sample text.
897
898 Instance attributes accessed:
899 theme_elements
900 is_builtin_theme
901 builtin_theme
902 custom_theme
903
904 Attributes updated:
905 highlight_sample: Set the tag elements to the theme.
906
907 Methods:
908 set_color_sample
909
910 Called from:
911 var_changed_builtin_theme
912 var_changed_custom_theme
913 load_theme_cfg
914 """
915 if self.is_builtin_theme.get(): # Default theme
916 theme = self.builtin_theme.get()
917 else: # User theme
918 theme = self.custom_theme.get()
919 for element_title in self.theme_elements:
920 element = self.theme_elements[element_title][0]
921 colors = idleConf.GetHighlight(theme, element)
922 if element == 'cursor': # Cursor sample needs special painting.
923 colors['background'] = idleConf.GetHighlight(
924 theme, 'normal', fgBg='bg')
925 # Handle any unsaved changes to this theme.
926 if theme in changes['highlight']:
927 theme_dict = changes['highlight'][theme]
928 if element + '-foreground' in theme_dict:
929 colors['foreground'] = theme_dict[element + '-foreground']
930 if element + '-background' in theme_dict:
931 colors['background'] = theme_dict[element + '-background']
932 self.highlight_sample.tag_config(element, **colors)
933 self.set_color_sample()
934
935 def save_new_theme(self, theme_name, theme):
936 """Save a newly created theme to idleConf.
937
938 theme_name - string, the name of the new theme
939 theme - dictionary containing the new theme
940 """
941 if not idleConf.userCfg['highlight'].has_section(theme_name):
942 idleConf.userCfg['highlight'].add_section(theme_name)
943 for element in theme:
944 value = theme[element]
945 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
946
947 def delete_custom_theme(self):
948 """Handle event to delete custom theme.
949
950 The current theme is deactivated and the default theme is
951 activated. The custom theme is permanently removed from
952 the config file.
953
954 Attributes accessed:
955 custom_theme
956
957 Attributes updated:
958 radio_theme_custom
959 opt_menu_theme_custom
960 is_builtin_theme
961 builtin_theme
962
963 Methods:
964 deactivate_current_config
965 save_all_changed_extensions
966 activate_config_changes
967 set_theme_type
968 """
969 theme_name = self.custom_theme.get()
970 delmsg = 'Are you sure you wish to delete the theme %r ?'
971 if not tkMessageBox.askyesno(
972 'Delete Theme', delmsg % theme_name, parent=self):
973 return
974 self.deactivate_current_config()
975 # Remove theme from changes, config, and file.
976 changes.delete_section('highlight', theme_name)
977 # Reload user theme list.
978 item_list = idleConf.GetSectionList('user', 'highlight')
979 item_list.sort()
980 if not item_list:
981 self.radio_theme_custom['state'] = DISABLED
982 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
983 else:
984 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
985 # Revert to default theme.
986 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
987 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
988 # User can't back out of these changes, they must be applied now.
989 changes.save_all()
990 self.save_all_changed_extensions()
991 self.activate_config_changes()
992 self.set_theme_type()
993
994
csabellabac7d332017-06-26 17:46:26 -0400995 def create_page_keys(self):
csabella7eb58832017-07-04 21:30:58 -0400996 """Return frame of widgets for Keys tab.
997
csabella36329a42017-07-13 23:32:01 -0400998 Tk Variables:
csabella7eb58832017-07-04 21:30:58 -0400999 builtin_keys: Menu variable for built-in keybindings.
1000 custom_keys: Menu variable for custom keybindings.
1001 are_keys_builtin: Selector for built-in or custom keybindings.
1002 keybinding: Action/key bindings.
csabella36329a42017-07-13 23:32:01 -04001003
1004 Methods:
1005 load_key_config: Set table.
1006 load_keys_list: Reload active set.
1007 keybinding_selected: Bound to list_bindings button release.
1008 get_new_keys: Command for button_new_keys.
1009 get_new_keys_name: Call popup.
1010 create_new_key_set: Combine active keyset and changes.
1011 set_keys_type: Command for are_keys_builtin.
1012 delete_custom_keys: Command for button_delete_custom_keys.
1013 save_as_new_key_set: Command for button_save_custom_keys.
1014 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1015 deactivate_current_config: Remove keys bindings in editors.
1016
1017 Widget Structure: (*) widgets bound to self
1018 frame
1019 frame_custom: LabelFrame
1020 frame_target: Frame
1021 target_title: Label
1022 scroll_target_y: Scrollbar
1023 scroll_target_x: Scrollbar
1024 (*)list_bindings: ListBox
1025 (*)button_new_keys: Button
1026 frame_key_sets: LabelFrame
1027 frames[0]: Frame
1028 (*)radio_keys_builtin: Radiobutton - are_keys_builtin
1029 (*)radio_keys_custom: Radiobutton - are_keys_builtin
1030 (*)opt_menu_keys_builtin: DynOptionMenu - builtin_keys
1031 (*)opt_menu_keys_custom: DynOptionMenu - custom_keys
1032 (*)new_custom_keys: Label
1033 frames[1]: Frame
1034 (*)button_delete_custom_keys: Button
1035 button_save_custom_keys: Button
csabella7eb58832017-07-04 21:30:58 -04001036 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001037 parent = self.parent
csabella5b591542017-07-28 14:40:59 -04001038 self.builtin_keys = tracers.add(
1039 StringVar(parent), self.var_changed_builtin_keys)
1040 self.custom_keys = tracers.add(
1041 StringVar(parent), self.var_changed_custom_keys)
1042 self.are_keys_builtin = tracers.add(
1043 BooleanVar(parent), self.var_changed_are_keys_builtin)
1044 self.keybinding = tracers.add(
1045 StringVar(parent), self.var_changed_keybinding)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001046
Steven M. Gava60fc7072001-08-04 13:58:22 +00001047 ##widget creation
1048 #body frame
csabellabac7d332017-06-26 17:46:26 -04001049 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +00001050 #body section frames
csabellabac7d332017-06-26 17:46:26 -04001051 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001052 frame, borderwidth=2, relief=GROOVE,
1053 text=' Custom Key Bindings ')
csabellabac7d332017-06-26 17:46:26 -04001054 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001055 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
csabellabac7d332017-06-26 17:46:26 -04001056 #frame_custom
1057 frame_target = Frame(frame_custom)
1058 target_title = Label(frame_target, text='Action - Key(s)')
1059 scroll_target_y = Scrollbar(frame_target)
1060 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1061 self.list_bindings = Listbox(
1062 frame_target, takefocus=FALSE, exportselection=FALSE)
1063 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
1064 scroll_target_y.config(command=self.list_bindings.yview)
1065 scroll_target_x.config(command=self.list_bindings.xview)
1066 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
1067 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
1068 self.button_new_keys = Button(
1069 frame_custom, text='Get New Keys for Selection',
1070 command=self.get_new_keys, state=DISABLED)
1071 #frame_key_sets
1072 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +00001073 for i in range(2)]
csabellabac7d332017-06-26 17:46:26 -04001074 self.radio_keys_builtin = Radiobutton(
1075 frames[0], variable=self.are_keys_builtin, value=1,
1076 command=self.set_keys_type, text='Use a Built-in Key Set')
1077 self.radio_keys_custom = Radiobutton(
1078 frames[0], variable=self.are_keys_builtin, value=0,
1079 command=self.set_keys_type, text='Use a Custom Key Set')
1080 self.opt_menu_keys_builtin = DynOptionMenu(
1081 frames[0], self.builtin_keys, None, command=None)
1082 self.opt_menu_keys_custom = DynOptionMenu(
1083 frames[0], self.custom_keys, None, command=None)
1084 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001085 frames[1], text='Delete Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001086 command=self.delete_custom_keys)
1087 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001088 frames[1], text='Save as New Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001089 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001090 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001091
Steven M. Gava60fc7072001-08-04 13:58:22 +00001092 ##widget packing
1093 #body
csabellabac7d332017-06-26 17:46:26 -04001094 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1095 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1096 #frame_custom
1097 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1098 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +00001099 #frame target
csabellabac7d332017-06-26 17:46:26 -04001100 frame_target.columnconfigure(0, weight=1)
1101 frame_target.rowconfigure(1, weight=1)
1102 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1103 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
1104 scroll_target_y.grid(row=1, column=1, sticky=NS)
1105 scroll_target_x.grid(row=2, column=0, sticky=EW)
1106 #frame_key_sets
1107 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
1108 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
1109 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
1110 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001111 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001112 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1113 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +00001114 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1115 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +00001116 return frame
1117
Terry Jan Reedyb1660802017-07-27 18:28:01 -04001118 def load_key_cfg(self):
1119 "Load current configuration settings for the keybinding options."
1120 # Set current keys type radiobutton.
1121 self.are_keys_builtin.set(idleConf.GetOption(
1122 'main', 'Keys', 'default', type='bool', default=1))
1123 # Set current keys.
1124 current_option = idleConf.CurrentKeys()
1125 # Load available keyset option menus.
1126 if self.are_keys_builtin.get(): # Default theme selected.
1127 item_list = idleConf.GetSectionList('default', 'keys')
1128 item_list.sort()
1129 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1130 item_list = idleConf.GetSectionList('user', 'keys')
1131 item_list.sort()
1132 if not item_list:
1133 self.radio_keys_custom['state'] = DISABLED
1134 self.custom_keys.set('- no custom keys -')
1135 else:
1136 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1137 else: # User key set selected.
1138 item_list = idleConf.GetSectionList('user', 'keys')
1139 item_list.sort()
1140 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1141 item_list = idleConf.GetSectionList('default', 'keys')
1142 item_list.sort()
1143 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1144 self.set_keys_type()
1145 # Load keyset element list.
1146 keyset_name = idleConf.CurrentKeys()
1147 self.load_keys_list(keyset_name)
1148
Terry Jan Reedyb1660802017-07-27 18:28:01 -04001149 def var_changed_builtin_keys(self, *params):
1150 "Process selection of builtin key set."
1151 old_keys = (
1152 'IDLE Classic Windows',
1153 'IDLE Classic Unix',
1154 'IDLE Classic Mac',
1155 'IDLE Classic OSX',
1156 )
1157 value = self.builtin_keys.get()
1158 if value not in old_keys:
1159 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1160 changes.add_option('main', 'Keys', 'name', old_keys[0])
1161 changes.add_option('main', 'Keys', 'name2', value)
1162 self.new_custom_keys.config(text='New key set, see Help',
1163 fg='#500000')
1164 else:
1165 changes.add_option('main', 'Keys', 'name', value)
1166 changes.add_option('main', 'Keys', 'name2', '')
1167 self.new_custom_keys.config(text='', fg='black')
1168 self.load_keys_list(value)
1169
1170 def var_changed_custom_keys(self, *params):
1171 "Process selection of custom key set."
1172 value = self.custom_keys.get()
1173 if value != '- no custom keys -':
1174 changes.add_option('main', 'Keys', 'name', value)
1175 self.load_keys_list(value)
1176
1177 def var_changed_are_keys_builtin(self, *params):
1178 "Process toggle between builtin key set and custom key set."
1179 value = self.are_keys_builtin.get()
1180 changes.add_option('main', 'Keys', 'default', value)
1181 if value:
1182 self.var_changed_builtin_keys()
1183 else:
1184 self.var_changed_custom_keys()
1185
1186 def var_changed_keybinding(self, *params):
1187 "Store change to a keybinding."
1188 value = self.keybinding.get()
1189 key_set = self.custom_keys.get()
1190 event = self.list_bindings.get(ANCHOR).split()[0]
1191 if idleConf.IsCoreBinding(event):
1192 changes.add_option('keys', key_set, event, value)
1193 else: # Event is an extension binding.
1194 ext_name = idleConf.GetExtnNameForEvent(event)
1195 ext_keybind_section = ext_name + '_cfgBindings'
1196 changes.add_option('extensions', ext_keybind_section, event, value)
1197
1198 def set_keys_type(self):
1199 "Set available screen options based on builtin or custom key set."
1200 if self.are_keys_builtin.get():
1201 self.opt_menu_keys_builtin['state'] = NORMAL
1202 self.opt_menu_keys_custom['state'] = DISABLED
1203 self.button_delete_custom_keys['state'] = DISABLED
1204 else:
1205 self.opt_menu_keys_builtin['state'] = DISABLED
1206 self.radio_keys_custom['state'] = NORMAL
1207 self.opt_menu_keys_custom['state'] = NORMAL
1208 self.button_delete_custom_keys['state'] = NORMAL
1209
1210 def get_new_keys(self):
1211 """Handle event to change key binding for selected line.
1212
1213 A selection of a key/binding in the list of current
1214 bindings pops up a dialog to enter a new binding. If
1215 the current key set is builtin and a binding has
1216 changed, then a name for a custom key set needs to be
1217 entered for the change to be applied.
1218 """
1219 list_index = self.list_bindings.index(ANCHOR)
1220 binding = self.list_bindings.get(list_index)
1221 bind_name = binding.split()[0]
1222 if self.are_keys_builtin.get():
1223 current_key_set_name = self.builtin_keys.get()
1224 else:
1225 current_key_set_name = self.custom_keys.get()
1226 current_bindings = idleConf.GetCurrentKeySet()
1227 if current_key_set_name in changes['keys']: # unsaved changes
1228 key_set_changes = changes['keys'][current_key_set_name]
1229 for event in key_set_changes:
1230 current_bindings[event] = key_set_changes[event].split()
1231 current_key_sequences = list(current_bindings.values())
1232 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1233 current_key_sequences).result
1234 if new_keys:
1235 if self.are_keys_builtin.get(): # Current key set is a built-in.
1236 message = ('Your changes will be saved as a new Custom Key Set.'
1237 ' Enter a name for your new Custom Key Set below.')
1238 new_keyset = self.get_new_keys_name(message)
1239 if not new_keyset: # User cancelled custom key set creation.
1240 self.list_bindings.select_set(list_index)
1241 self.list_bindings.select_anchor(list_index)
1242 return
1243 else: # Create new custom key set based on previously active key set.
1244 self.create_new_key_set(new_keyset)
1245 self.list_bindings.delete(list_index)
1246 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
1247 self.list_bindings.select_set(list_index)
1248 self.list_bindings.select_anchor(list_index)
1249 self.keybinding.set(new_keys)
1250 else:
1251 self.list_bindings.select_set(list_index)
1252 self.list_bindings.select_anchor(list_index)
1253
1254 def get_new_keys_name(self, message):
1255 "Return new key set name from query popup."
1256 used_names = (idleConf.GetSectionList('user', 'keys') +
1257 idleConf.GetSectionList('default', 'keys'))
1258 new_keyset = SectionName(
1259 self, 'New Custom Key Set', message, used_names).result
1260 return new_keyset
1261
1262 def save_as_new_key_set(self):
1263 "Prompt for name of new key set and save changes using that name."
1264 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1265 if new_keys_name:
1266 self.create_new_key_set(new_keys_name)
1267
1268 def keybinding_selected(self, event):
1269 "Activate button to assign new keys to selected action."
1270 self.button_new_keys['state'] = NORMAL
1271
1272 def create_new_key_set(self, new_key_set_name):
1273 """Create a new custom key set with the given name.
1274
1275 Create the new key set based on the previously active set
1276 with the current changes applied. Once it is saved, then
1277 activate the new key set.
1278 """
1279 if self.are_keys_builtin.get():
1280 prev_key_set_name = self.builtin_keys.get()
1281 else:
1282 prev_key_set_name = self.custom_keys.get()
1283 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1284 new_keys = {}
1285 for event in prev_keys: # Add key set to changed items.
1286 event_name = event[2:-2] # Trim off the angle brackets.
1287 binding = ' '.join(prev_keys[event])
1288 new_keys[event_name] = binding
1289 # Handle any unsaved changes to prev key set.
1290 if prev_key_set_name in changes['keys']:
1291 key_set_changes = changes['keys'][prev_key_set_name]
1292 for event in key_set_changes:
1293 new_keys[event] = key_set_changes[event]
1294 # Save the new key set.
1295 self.save_new_key_set(new_key_set_name, new_keys)
1296 # Change GUI over to the new key set.
1297 custom_key_list = idleConf.GetSectionList('user', 'keys')
1298 custom_key_list.sort()
1299 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
1300 self.are_keys_builtin.set(0)
1301 self.set_keys_type()
1302
1303 def load_keys_list(self, keyset_name):
1304 """Reload the list of action/key binding pairs for the active key set.
1305
1306 An action/key binding can be selected to change the key binding.
1307 """
1308 reselect = 0
1309 if self.list_bindings.curselection():
1310 reselect = 1
1311 list_index = self.list_bindings.index(ANCHOR)
1312 keyset = idleConf.GetKeySet(keyset_name)
1313 bind_names = list(keyset.keys())
1314 bind_names.sort()
1315 self.list_bindings.delete(0, END)
1316 for bind_name in bind_names:
1317 key = ' '.join(keyset[bind_name])
1318 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1319 if keyset_name in changes['keys']:
1320 # Handle any unsaved changes to this key set.
1321 if bind_name in changes['keys'][keyset_name]:
1322 key = changes['keys'][keyset_name][bind_name]
1323 self.list_bindings.insert(END, bind_name+' - '+key)
1324 if reselect:
1325 self.list_bindings.see(list_index)
1326 self.list_bindings.select_set(list_index)
1327 self.list_bindings.select_anchor(list_index)
1328
1329 def save_new_key_set(self, keyset_name, keyset):
1330 """Save a newly created core key set.
1331
1332 keyset_name - string, the name of the new key set
1333 keyset - dictionary containing the new key set
1334 """
1335 if not idleConf.userCfg['keys'].has_section(keyset_name):
1336 idleConf.userCfg['keys'].add_section(keyset_name)
1337 for event in keyset:
1338 value = keyset[event]
1339 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1340
1341 def delete_custom_keys(self):
1342 """Handle event to delete a custom key set.
1343
1344 Applying the delete deactivates the current configuration and
1345 reverts to the default. The custom key set is permanently
1346 deleted from the config file.
1347 """
1348 keyset_name=self.custom_keys.get()
1349 delmsg = 'Are you sure you wish to delete the key set %r ?'
1350 if not tkMessageBox.askyesno(
1351 'Delete Key Set', delmsg % keyset_name, parent=self):
1352 return
1353 self.deactivate_current_config()
1354 # Remove key set from changes, config, and file.
1355 changes.delete_section('keys', keyset_name)
1356 # Reload user key set list.
1357 item_list = idleConf.GetSectionList('user', 'keys')
1358 item_list.sort()
1359 if not item_list:
1360 self.radio_keys_custom['state'] = DISABLED
1361 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
1362 else:
1363 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1364 # Revert to default key set.
1365 self.are_keys_builtin.set(idleConf.defaultCfg['main']
1366 .Get('Keys', 'default'))
1367 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
1368 or idleConf.default_keys())
1369 # User can't back out of these changes, they must be applied now.
1370 changes.save_all()
1371 self.save_all_changed_extensions()
1372 self.activate_config_changes()
1373 self.set_keys_type()
1374
1375 def deactivate_current_config(self):
1376 """Remove current key bindings.
1377
1378 Iterate over window instances defined in parent and remove
1379 the keybindings.
1380 """
1381 # Before a config is saved, some cleanup of current
1382 # config must be done - remove the previous keybindings.
1383 win_instances = self.parent.instance_dict.keys()
1384 for instance in win_instances:
1385 instance.RemoveKeybindings()
1386
1387 def activate_config_changes(self):
1388 """Apply configuration changes to current windows.
1389
1390 Dynamically update the current parent window instances
1391 with some of the configuration changes.
1392 """
1393 win_instances = self.parent.instance_dict.keys()
1394 for instance in win_instances:
1395 instance.ResetColorizer()
1396 instance.ResetFont()
1397 instance.set_notabs_indentwidth()
1398 instance.ApplyKeybindings()
1399 instance.reset_help_menu_entries()
1400
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001401
csabellabac7d332017-06-26 17:46:26 -04001402 def create_page_general(self):
csabella7eb58832017-07-04 21:30:58 -04001403 """Return frame of widgets for General tab.
1404
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001405 Enable users to provisionally change general options. Function
1406 load_general_cfg intializes tk variables and helplist using
1407 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1408 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1409 set var autosave. Entry boxes win_width_int and win_height_int
1410 set var win_width and win_height. Setting var_name invokes the
csabella5b591542017-07-28 14:40:59 -04001411 default callback that adds option to changes.
csabella36329a42017-07-13 23:32:01 -04001412
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001413 Helplist: load_general_cfg loads list user_helplist with
1414 name, position pairs and copies names to listbox helplist.
1415 Clicking a name invokes help_source selected. Clicking
1416 button_helplist_name invokes helplist_item_name, which also
1417 changes user_helplist. These functions all call
1418 set_add_delete_state. All but load call update_help_changes to
1419 rewrite changes['main']['HelpFiles'].
csabella36329a42017-07-13 23:32:01 -04001420
1421 Widget Structure: (*) widgets bound to self
1422 frame
1423 frame_run: LabelFrame
1424 startup_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001425 (*)startup_editor_on: Radiobutton - startup_edit
1426 (*)startup_shell_on: Radiobutton - startup_edit
csabella36329a42017-07-13 23:32:01 -04001427 frame_save: LabelFrame
1428 run_save_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001429 (*)save_ask_on: Radiobutton - autosave
1430 (*)save_auto_on: Radiobutton - autosave
csabella36329a42017-07-13 23:32:01 -04001431 frame_win_size: LabelFrame
1432 win_size_title: Label
1433 win_width_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001434 (*)win_width_int: Entry - win_width
csabella36329a42017-07-13 23:32:01 -04001435 win_height_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001436 (*)win_height_int: Entry - win_height
csabella36329a42017-07-13 23:32:01 -04001437 frame_help: LabelFrame
1438 frame_helplist: Frame
1439 frame_helplist_buttons: Frame
1440 (*)button_helplist_edit
1441 (*)button_helplist_add
1442 (*)button_helplist_remove
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001443 (*)helplist: ListBox
csabella36329a42017-07-13 23:32:01 -04001444 scroll_helplist: Scrollbar
csabella7eb58832017-07-04 21:30:58 -04001445 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001446 parent = self.parent
csabella5b591542017-07-28 14:40:59 -04001447 self.startup_edit = tracers.add(
1448 IntVar(parent), ('main', 'General', 'editor-on-startup'))
1449 self.autosave = tracers.add(
1450 IntVar(parent), ('main', 'General', 'autosave'))
1451 self.win_width = tracers.add(
1452 StringVar(parent), ('main', 'EditorWindow', 'width'))
1453 self.win_height = tracers.add(
1454 StringVar(parent), ('main', 'EditorWindow', 'height'))
Terry Jan Reedy22405332014-07-30 19:24:32 -04001455
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001456 # Create widgets:
1457 # body.
csabellabac7d332017-06-26 17:46:26 -04001458 frame = self.tab_pages.pages['General'].frame
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001459 # body section frames.
csabellabac7d332017-06-26 17:46:26 -04001460 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001461 text=' Startup Preferences ')
csabellabac7d332017-06-26 17:46:26 -04001462 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
1463 text=' autosave Preferences ')
1464 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
1465 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001466 text=' Additional Help Sources ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001467 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001468 startup_title = Label(frame_run, text='At Startup')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001469 self.startup_editor_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001470 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001471 text="Open Edit Window")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001472 self.startup_shell_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001473 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001474 text='Open Shell Window')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001475 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001476 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001477 self.save_ask_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001478 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001479 text="Prompt to Save")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001480 self.save_auto_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001481 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001482 text='No Prompt')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001483 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001484 win_size_title = Label(
1485 frame_win_size, text='Initial Window Size (in characters)')
1486 win_width_title = Label(frame_win_size, text='Width')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001487 self.win_width_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001488 frame_win_size, textvariable=self.win_width, width=3)
1489 win_height_title = Label(frame_win_size, text='Height')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001490 self.win_height_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001491 frame_win_size, textvariable=self.win_height, width=3)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001492 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001493 frame_helplist = Frame(frame_help)
1494 frame_helplist_buttons = Frame(frame_helplist)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001495 self.helplist = Listbox(
csabellabac7d332017-06-26 17:46:26 -04001496 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001497 exportselection=FALSE)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001498 scroll_helplist = Scrollbar(frame_helplist)
1499 scroll_helplist['command'] = self.helplist.yview
1500 self.helplist['yscrollcommand'] = scroll_helplist.set
1501 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
csabellabac7d332017-06-26 17:46:26 -04001502 self.button_helplist_edit = Button(
1503 frame_helplist_buttons, text='Edit', state=DISABLED,
1504 width=8, command=self.helplist_item_edit)
1505 self.button_helplist_add = Button(
1506 frame_helplist_buttons, text='Add',
1507 width=8, command=self.helplist_item_add)
1508 self.button_helplist_remove = Button(
1509 frame_helplist_buttons, text='Remove', state=DISABLED,
1510 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001511
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001512 # Pack widgets:
1513 # body.
csabellabac7d332017-06-26 17:46:26 -04001514 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
1515 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
1516 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
1517 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001518 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001519 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001520 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1521 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1522 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001523 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001524 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1525 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1526 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001527 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001528 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001529 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001530 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001531 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001532 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001533 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
1534 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1535 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001536 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
csabellabac7d332017-06-26 17:46:26 -04001537 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
1538 self.button_helplist_add.pack(side=TOP, anchor=W)
1539 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001540
Steven M. Gava952d0a52001-08-03 04:43:44 +00001541 return frame
1542
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001543 def load_general_cfg(self):
1544 "Load current configuration settings for the general options."
1545 # Set startup state.
1546 self.startup_edit.set(idleConf.GetOption(
1547 'main', 'General', 'editor-on-startup', default=0, type='bool'))
1548 # Set autosave state.
1549 self.autosave.set(idleConf.GetOption(
1550 'main', 'General', 'autosave', default=0, type='bool'))
1551 # Set initial window size.
1552 self.win_width.set(idleConf.GetOption(
1553 'main', 'EditorWindow', 'width', type='int'))
1554 self.win_height.set(idleConf.GetOption(
1555 'main', 'EditorWindow', 'height', type='int'))
1556 # Set additional help sources.
1557 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1558 self.helplist.delete(0, 'end')
1559 for help_item in self.user_helplist:
1560 self.helplist.insert(END, help_item[0])
1561 self.set_add_delete_state()
1562
1563 def var_changed_startup_edit(self, *params):
1564 "Store change to toggle for starting IDLE in the editor or shell."
1565 value = self.startup_edit.get()
1566 changes.add_option('main', 'General', 'editor-on-startup', value)
1567
1568 def var_changed_autosave(self, *params):
1569 "Store change to autosave."
1570 value = self.autosave.get()
1571 changes.add_option('main', 'General', 'autosave', value)
1572
1573 def var_changed_win_width(self, *params):
1574 "Store change to window width."
1575 value = self.win_width.get()
1576 changes.add_option('main', 'EditorWindow', 'width', value)
1577
1578 def var_changed_win_height(self, *params):
1579 "Store change to window height."
1580 value = self.win_height.get()
1581 changes.add_option('main', 'EditorWindow', 'height', value)
1582
1583 def help_source_selected(self, event):
1584 "Handle event for selecting additional help."
1585 self.set_add_delete_state()
1586
1587 def set_add_delete_state(self):
1588 "Toggle the state for the help list buttons based on list entries."
1589 if self.helplist.size() < 1: # No entries in list.
1590 self.button_helplist_edit['state'] = DISABLED
1591 self.button_helplist_remove['state'] = DISABLED
1592 else: # Some entries.
1593 if self.helplist.curselection(): # There currently is a selection.
1594 self.button_helplist_edit['state'] = NORMAL
1595 self.button_helplist_remove['state'] = NORMAL
1596 else: # There currently is not a selection.
1597 self.button_helplist_edit['state'] = DISABLED
1598 self.button_helplist_remove['state'] = DISABLED
1599
1600 def helplist_item_add(self):
1601 """Handle add button for the help list.
1602
1603 Query for name and location of new help sources and add
1604 them to the list.
1605 """
1606 help_source = HelpSource(self, 'New Help Source').result
1607 if help_source:
1608 self.user_helplist.append(help_source)
1609 self.helplist.insert(END, help_source[0])
1610 self.update_help_changes()
1611
1612 def helplist_item_edit(self):
1613 """Handle edit button for the help list.
1614
1615 Query with existing help source information and update
1616 config if the values are changed.
1617 """
1618 item_index = self.helplist.index(ANCHOR)
1619 help_source = self.user_helplist[item_index]
1620 new_help_source = HelpSource(
1621 self, 'Edit Help Source',
1622 menuitem=help_source[0],
1623 filepath=help_source[1],
1624 ).result
1625 if new_help_source and new_help_source != help_source:
1626 self.user_helplist[item_index] = new_help_source
1627 self.helplist.delete(item_index)
1628 self.helplist.insert(item_index, new_help_source[0])
1629 self.update_help_changes()
1630 self.set_add_delete_state() # Selected will be un-selected
1631
1632 def helplist_item_remove(self):
1633 """Handle remove button for the help list.
1634
1635 Delete the help list item from config.
1636 """
1637 item_index = self.helplist.index(ANCHOR)
1638 del(self.user_helplist[item_index])
1639 self.helplist.delete(item_index)
1640 self.update_help_changes()
1641 self.set_add_delete_state()
1642
1643 def update_help_changes(self):
1644 "Clear and rebuild the HelpFiles section in changes"
1645 changes['main']['HelpFiles'] = {}
1646 for num in range(1, len(self.user_helplist) + 1):
1647 changes.add_option(
1648 'main', 'HelpFiles', str(num),
1649 ';'.join(self.user_helplist[num-1][:2]))
1650
1651
csabellabac7d332017-06-26 17:46:26 -04001652 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001653 """Part of the config dialog used for configuring IDLE extensions.
1654
1655 This code is generic - it works for any and all IDLE extensions.
1656
1657 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001658 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001659 GUI interface to change the configuration values, and saves the
1660 changes using idleConf.
1661
1662 Not all changes take effect immediately - some may require restarting IDLE.
1663 This depends on each extension's implementation.
1664
1665 All values are treated as text, and it is up to the user to supply
1666 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001667 which are boolean, and can be toggled with a True/False button.
csabella36329a42017-07-13 23:32:01 -04001668
1669 Methods:
1670 load_extentions:
1671 extension_selected: Handle selection from list.
1672 create_extension_frame: Hold widgets for one extension.
1673 set_extension_value: Set in userCfg['extensions'].
1674 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001675 """
1676 parent = self.parent
csabellabac7d332017-06-26 17:46:26 -04001677 frame = self.tab_pages.pages['Extensions'].frame
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001678 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1679 self.ext_userCfg = idleConf.userCfg['extensions']
1680 self.is_int = self.register(is_int)
1681 self.load_extensions()
csabella7eb58832017-07-04 21:30:58 -04001682 # Create widgets - a listbox shows all available extensions, with the
1683 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001684 self.extension_names = StringVar(self)
1685 frame.rowconfigure(0, weight=1)
1686 frame.columnconfigure(2, weight=1)
1687 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1688 selectmode='browse')
1689 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1690 scroll = Scrollbar(frame, command=self.extension_list.yview)
1691 self.extension_list.yscrollcommand=scroll.set
1692 self.details_frame = LabelFrame(frame, width=250, height=250)
1693 self.extension_list.grid(column=0, row=0, sticky='nws')
1694 scroll.grid(column=1, row=0, sticky='ns')
1695 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1696 frame.configure(padx=10, pady=10)
1697 self.config_frame = {}
1698 self.current_extension = None
1699
1700 self.outerframe = self # TEMPORARY
1701 self.tabbed_page_set = self.extension_list # TEMPORARY
1702
csabella7eb58832017-07-04 21:30:58 -04001703 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001704 ext_names = ''
1705 for ext_name in sorted(self.extensions):
1706 self.create_extension_frame(ext_name)
1707 ext_names = ext_names + '{' + ext_name + '} '
1708 self.extension_names.set(ext_names)
1709 self.extension_list.selection_set(0)
1710 self.extension_selected(None)
1711
1712 def load_extensions(self):
1713 "Fill self.extensions with data from the default and user configs."
1714 self.extensions = {}
1715 for ext_name in idleConf.GetExtensions(active_only=False):
1716 self.extensions[ext_name] = []
1717
1718 for ext_name in self.extensions:
1719 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1720
csabella7eb58832017-07-04 21:30:58 -04001721 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001722 enables = [opt_name for opt_name in opt_list
1723 if opt_name.startswith('enable')]
1724 for opt_name in enables:
1725 opt_list.remove(opt_name)
1726 opt_list = enables + opt_list
1727
1728 for opt_name in opt_list:
1729 def_str = self.ext_defaultCfg.Get(
1730 ext_name, opt_name, raw=True)
1731 try:
1732 def_obj = {'True':True, 'False':False}[def_str]
1733 opt_type = 'bool'
1734 except KeyError:
1735 try:
1736 def_obj = int(def_str)
1737 opt_type = 'int'
1738 except ValueError:
1739 def_obj = def_str
1740 opt_type = None
1741 try:
1742 value = self.ext_userCfg.Get(
1743 ext_name, opt_name, type=opt_type, raw=True,
1744 default=def_obj)
csabella7eb58832017-07-04 21:30:58 -04001745 except ValueError: # Need this until .Get fixed.
1746 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001747 var = StringVar(self)
1748 var.set(str(value))
1749
1750 self.extensions[ext_name].append({'name': opt_name,
1751 'type': opt_type,
1752 'default': def_str,
1753 'value': value,
1754 'var': var,
1755 })
1756
1757 def extension_selected(self, event):
csabella7eb58832017-07-04 21:30:58 -04001758 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001759 newsel = self.extension_list.curselection()
1760 if newsel:
1761 newsel = self.extension_list.get(newsel)
1762 if newsel is None or newsel != self.current_extension:
1763 if self.current_extension:
1764 self.details_frame.config(text='')
1765 self.config_frame[self.current_extension].grid_forget()
1766 self.current_extension = None
1767 if newsel:
1768 self.details_frame.config(text=newsel)
1769 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1770 self.current_extension = newsel
1771
1772 def create_extension_frame(self, ext_name):
1773 """Create a frame holding the widgets to configure one extension"""
1774 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1775 self.config_frame[ext_name] = f
1776 entry_area = f.interior
csabella7eb58832017-07-04 21:30:58 -04001777 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001778 for row, opt in enumerate(self.extensions[ext_name]):
csabella7eb58832017-07-04 21:30:58 -04001779 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001780 label = Label(entry_area, text=opt['name'])
1781 label.grid(row=row, column=0, sticky=NW)
1782 var = opt['var']
1783 if opt['type'] == 'bool':
1784 Checkbutton(entry_area, textvariable=var, variable=var,
1785 onvalue='True', offvalue='False',
1786 indicatoron=FALSE, selectcolor='', width=8
1787 ).grid(row=row, column=1, sticky=W, padx=7)
1788 elif opt['type'] == 'int':
1789 Entry(entry_area, textvariable=var, validate='key',
1790 validatecommand=(self.is_int, '%P')
1791 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1792
1793 else:
1794 Entry(entry_area, textvariable=var
1795 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1796 return
1797
1798 def set_extension_value(self, section, opt):
csabella7eb58832017-07-04 21:30:58 -04001799 """Return True if the configuration was added or changed.
1800
1801 If the value is the same as the default, then remove it
1802 from user config file.
1803 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001804 name = opt['name']
1805 default = opt['default']
1806 value = opt['var'].get().strip() or default
1807 opt['var'].set(value)
1808 # if self.defaultCfg.has_section(section):
csabella7eb58832017-07-04 21:30:58 -04001809 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001810 if (value == default):
1811 return self.ext_userCfg.RemoveOption(section, name)
csabella7eb58832017-07-04 21:30:58 -04001812 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001813 return self.ext_userCfg.SetOption(section, name, value)
1814
1815 def save_all_changed_extensions(self):
csabella36329a42017-07-13 23:32:01 -04001816 """Save configuration changes to the user config file.
1817
1818 Attributes accessed:
1819 extensions
1820
1821 Methods:
1822 set_extension_value
1823 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001824 has_changes = False
1825 for ext_name in self.extensions:
1826 options = self.extensions[ext_name]
1827 for opt in options:
1828 if self.set_extension_value(ext_name, opt):
1829 has_changes = True
1830 if has_changes:
1831 self.ext_userCfg.Save()
1832
1833
csabella45bf7232017-07-26 19:09:58 -04001834class VarTrace:
1835 """Maintain Tk variables trace state."""
1836
1837 def __init__(self):
1838 """Store Tk variables and callbacks.
1839
1840 untraced: List of tuples (var, callback)
1841 that do not have the callback attached
1842 to the Tk var.
1843 traced: List of tuples (var, callback) where
1844 that callback has been attached to the var.
1845 """
1846 self.untraced = []
1847 self.traced = []
1848
1849 def add(self, var, callback):
1850 """Add (var, callback) tuple to untraced list.
1851
1852 Args:
1853 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04001854 callback: Either function name to be used as a callback
1855 or a tuple with IdleConf config-type, section, and
1856 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04001857
1858 Return:
1859 Tk variable instance.
1860 """
1861 if isinstance(callback, tuple):
1862 callback = self.make_callback(var, callback)
1863 self.untraced.append((var, callback))
1864 return var
1865
1866 @staticmethod
1867 def make_callback(var, config):
1868 "Return default callback function to add values to changes instance."
1869 def default_callback(*params):
1870 "Add config values to changes instance."
1871 changes.add_option(*config, var.get())
1872 return default_callback
1873
1874 def attach(self):
1875 "Attach callback to all vars that are not traced."
1876 while self.untraced:
1877 var, callback = self.untraced.pop()
1878 var.trace_add('write', callback)
1879 self.traced.append((var, callback))
1880
1881 def detach(self):
1882 "Remove callback from traced vars."
1883 while self.traced:
1884 var, callback = self.traced.pop()
1885 var.trace_remove('write', var.trace_info()[0][1])
1886 self.untraced.append((var, callback))
1887
1888
csabella5b591542017-07-28 14:40:59 -04001889tracers = VarTrace()
1890
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001891help_common = '''\
1892When you click either the Apply or Ok buttons, settings in this
1893dialog that are different from IDLE's default are saved in
1894a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001895these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001896machine. Some do not take affect until IDLE is restarted.
1897[Cancel] only cancels changes made since the last save.
1898'''
1899help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001900 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001901Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001902The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001903be used with older IDLE releases if it is saved as a custom
1904theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001905''',
1906 'Keys': '''
1907Keys:
1908The IDLE Modern Unix key set is new in June 2016. It can only
1909be used with older IDLE releases if it is saved as a custom
1910key set, with a different name.
1911''',
wohlgangerfae2c352017-06-27 21:36:23 -05001912 'Extensions': '''
1913Extensions:
1914
1915Autocomplete: Popupwait is milleseconds to wait after key char, without
1916cursor movement, before popping up completion box. Key char is '.' after
1917identifier or a '/' (or '\\' on Windows) within a string.
1918
1919FormatParagraph: Max-width is max chars in lines after re-formatting.
1920Use with paragraphs in both strings and comment blocks.
1921
1922ParenMatch: Style indicates what is highlighted when closer is entered:
1923'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1924'expression' (default) - also everything in between. Flash-delay is how
1925long to highlight if cursor is not moved (0 means forever).
1926'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001927}
1928
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001929
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001930def is_int(s):
1931 "Return 's is blank or represents an int'"
1932 if not s:
1933 return True
1934 try:
1935 int(s)
1936 return True
1937 except ValueError:
1938 return False
1939
1940
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001941class VerticalScrolledFrame(Frame):
1942 """A pure Tkinter vertically scrollable frame.
1943
1944 * Use the 'interior' attribute to place widgets inside the scrollable frame
1945 * Construct and pack/place/grid normally
1946 * This frame only allows vertical scrolling
1947 """
1948 def __init__(self, parent, *args, **kw):
1949 Frame.__init__(self, parent, *args, **kw)
1950
csabella7eb58832017-07-04 21:30:58 -04001951 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001952 vscrollbar = Scrollbar(self, orient=VERTICAL)
1953 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1954 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001955 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001956 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1957 vscrollbar.config(command=canvas.yview)
1958
csabella7eb58832017-07-04 21:30:58 -04001959 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001960 canvas.xview_moveto(0)
1961 canvas.yview_moveto(0)
1962
csabella7eb58832017-07-04 21:30:58 -04001963 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001964 self.interior = interior = Frame(canvas)
1965 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1966
csabella7eb58832017-07-04 21:30:58 -04001967 # Track changes to the canvas and frame width and sync them,
1968 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001969 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04001970 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001971 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1972 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001973 interior.bind('<Configure>', _configure_interior)
1974
1975 def _configure_canvas(event):
1976 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04001977 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001978 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1979 canvas.bind('<Configure>', _configure_canvas)
1980
1981 return
1982
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001983
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00001984if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04001985 import unittest
1986 unittest.main('idlelib.idle_test.test_configdialog',
1987 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04001988 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04001989 run(ConfigDialog)