blob: 67165298a9963eb05df52c8329c8dac3f465ea4d [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 Reedyb331f802017-07-29 00:49:39 -040018from tkinter.ttk import Notebook, 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 activate_config_changes: Tell editors to reload.
103 """
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400104 self.note = note = Notebook(self, width=450, height=450)
105 fontpage = self.create_page_font_tab()
106 highpage = self.create_page_highlight()
107 keyspage = self.create_page_keys()
108 genpage = self.create_page_general()
109 extpage = self.create_page_extensions()
110 note.add(fontpage, text='Fonts/Tabs')
111 note.add(highpage, text='Highlights')
112 note.add(keyspage, text=' Keys ')
113 note.add(genpage, text=' General ')
114 note.add(extpage, text='Extensions')
115 note.enable_traversal()
116 note.pack(side=TOP, expand=TRUE, fill=BOTH)
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400117 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400118
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400119 def load_configs(self):
120 """Load configuration for each page.
121
122 Load configuration from default and user config files and populate
123 the widgets on the config dialog pages.
124
125 Methods:
126 load_font_cfg
127 load_tab_cfg
128 load_theme_cfg
129 load_key_cfg
130 load_general_cfg
131 """
132 self.load_font_cfg()
133 self.load_tab_cfg()
134 self.load_theme_cfg()
135 self.load_key_cfg()
136 self.load_general_cfg()
137 # note: extension page handled separately
138
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400139 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400140 """Return frame of action buttons for dialog.
141
142 Methods:
143 ok
144 apply
145 cancel
146 help
147
148 Widget Structure:
149 outer: Frame
150 buttons: Frame
151 (no assignment): Button (ok)
152 (no assignment): Button (apply)
153 (no assignment): Button (cancel)
154 (no assignment): Button (help)
155 (no assignment): Frame
156 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400157 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400158 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400159 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400160 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000161 else:
csabellabac7d332017-06-26 17:46:26 -0400162 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400163 outer = Frame(self, pady=2)
164 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400165 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400166 ('Ok', self.ok),
167 ('Apply', self.apply),
168 ('Cancel', self.cancel),
169 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400170 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
csabellabac7d332017-06-26 17:46:26 -0400171 **padding_args).pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400172 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400173 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
174 buttons.pack(side=BOTTOM)
175 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400176
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400177 def ok(self):
178 """Apply config changes, then dismiss dialog.
179
180 Methods:
181 apply
182 destroy: inherited
183 """
184 self.apply()
185 self.destroy()
186
187 def apply(self):
188 """Apply config changes and leave dialog open.
189
190 Methods:
191 deactivate_current_config
192 save_all_changed_extensions
193 activate_config_changes
194 """
195 self.deactivate_current_config()
196 changes.save_all()
197 self.save_all_changed_extensions()
198 self.activate_config_changes()
199
200 def cancel(self):
201 """Dismiss config dialog.
202
203 Methods:
204 destroy: inherited
205 """
206 self.destroy()
207
208 def help(self):
209 """Create textview for config dialog help.
210
211 Attrbutes accessed:
212 tab_pages
213
214 Methods:
215 view_text: Method from textview module.
216 """
217 page = self.tab_pages._current_page
218 view_text(self, title='Help for IDLE preferences',
219 text=help_common+help_pages.get(page, ''))
220
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400221
csabellabac7d332017-06-26 17:46:26 -0400222 def create_page_font_tab(self):
csabella7eb58832017-07-04 21:30:58 -0400223 """Return frame of widgets for Font/Tabs tab.
224
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400225 Fonts: Enable users to provisionally change font face, size, or
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400226 boldness and to see the consequence of proposed choices. Each
227 action set 3 options in changes structuree and changes the
228 corresponding aspect of the font sample on this page and
229 highlight sample on highlight page.
230
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400231 Funtion load_font_cfg initializes font vars and widgets from
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400232 idleConf entries and tk.
233
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400234 Fontlist: mouse button 1 click or up or down key invoke
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400235 on_fontlist_select(), which sets var font_name.
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400236
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400237 Sizelist: clicking the menubutton opens the dropdown menu. A
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400238 mouse button 1 click or return key sets var font_size.
csabella36329a42017-07-13 23:32:01 -0400239
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400240 Bold_toggle: clicking the box toggles var font_bold.
csabella36329a42017-07-13 23:32:01 -0400241
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400242 Changing any of the font vars invokes var_changed_font, which
243 adds all 3 font options to changes and calls set_samples.
244 Set_samples applies a new font constructed from the font vars to
245 font_sample and to highlight_sample on the hightlight page.
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400246
247 Tabs: Enable users to change spaces entered for indent tabs.
248 Changing indent_scale value with the mouse sets Var space_num,
csabella5b591542017-07-28 14:40:59 -0400249 which invokes the default callback to add an entry to
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400250 changes. Load_tab_cfg initializes space_num to default.
csabella36329a42017-07-13 23:32:01 -0400251
252 Widget Structure: (*) widgets bound to self
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400253 frame (of tab_pages)
csabella36329a42017-07-13 23:32:01 -0400254 frame_font: LabelFrame
255 frame_font_name: Frame
256 font_name_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400257 (*)fontlist: ListBox - font_name
csabella36329a42017-07-13 23:32:01 -0400258 scroll_font: Scrollbar
259 frame_font_param: Frame
260 font_size_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400261 (*)sizelist: DynOptionMenu - font_size
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400262 (*)bold_toggle: Checkbutton - font_bold
csabella36329a42017-07-13 23:32:01 -0400263 frame_font_sample: Frame
264 (*)font_sample: Label
265 frame_indent: LabelFrame
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400266 indent_title: Label
267 (*)indent_scale: Scale - space_num
csabella7eb58832017-07-04 21:30:58 -0400268 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400269 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400270 self.font_name = tracers.add(StringVar(parent), self.var_changed_font)
271 self.font_size = tracers.add(StringVar(parent), self.var_changed_font)
272 self.font_bold = tracers.add(BooleanVar(parent), self.var_changed_font)
273 self.space_num = tracers.add(IntVar(parent), ('main', 'Indent', 'num-spaces'))
Terry Jan Reedy22405332014-07-30 19:24:32 -0400274
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400275 # Create widgets:
Louie Lubb2bae82017-07-10 06:57:18 +0800276 # body and body section frames.
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400277 frame = Frame(self.note)
csabellabac7d332017-06-26 17:46:26 -0400278 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400279 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
csabellabac7d332017-06-26 17:46:26 -0400280 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400281 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400282 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400283 frame_font_name = Frame(frame_font)
284 frame_font_param = Frame(frame_font)
285 font_name_title = Label(
286 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400287 self.fontlist = Listbox(frame_font_name, height=5,
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400288 takefocus=True, exportselection=FALSE)
terryjreedy5b62b352017-07-11 01:58:04 -0400289 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
290 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
291 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
csabellabac7d332017-06-26 17:46:26 -0400292 scroll_font = Scrollbar(frame_font_name)
Louie Lubb2bae82017-07-10 06:57:18 +0800293 scroll_font.config(command=self.fontlist.yview)
294 self.fontlist.config(yscrollcommand=scroll_font.set)
csabellabac7d332017-06-26 17:46:26 -0400295 font_size_title = Label(frame_font_param, text='Size :')
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400296 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400297 self.bold_toggle = Checkbutton(
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400298 frame_font_param, variable=self.font_bold,
299 onvalue=1, offvalue=0, text='Bold')
csabellabac7d332017-06-26 17:46:26 -0400300 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400301 temp_font = tkFont.Font(parent, ('courier', 10, 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400302 self.font_sample = Label(
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400303 frame_font_sample, justify=LEFT, font=temp_font,
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400304 text='AaBbCcDdEe\nFfGgHhIiJj\n1234567890\n#:+=(){}[]')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400305 # frame_indent.
306 indent_title = Label(
307 frame_indent, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400308 text='Python Standard: 4 Spaces!')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400309 self.indent_scale = Scale(
310 frame_indent, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400311 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400312
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400313 # Pack widgets:
314 # body.
csabellabac7d332017-06-26 17:46:26 -0400315 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
316 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400317 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400318 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
319 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
320 font_name_title.pack(side=TOP, anchor=W)
Louie Lubb2bae82017-07-10 06:57:18 +0800321 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400322 scroll_font.pack(side=LEFT, fill=Y)
323 font_size_title.pack(side=LEFT, anchor=W)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400324 self.sizelist.pack(side=LEFT, anchor=W)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400325 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
csabellabac7d332017-06-26 17:46:26 -0400326 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
327 self.font_sample.pack(expand=TRUE, fill=BOTH)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400328 # frame_indent.
329 frame_indent.pack(side=TOP, fill=X)
330 indent_title.pack(side=TOP, anchor=W, padx=5)
331 self.indent_scale.pack(side=TOP, padx=5, fill=X)
Louie Lubb2bae82017-07-10 06:57:18 +0800332
Steven M. Gava952d0a52001-08-03 04:43:44 +0000333 return frame
334
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400335 def load_font_cfg(self):
336 """Load current configuration settings for the font options.
337
338 Retrieve current font with idleConf.GetFont and font families
339 from tk. Setup fontlist and set font_name. Setup sizelist,
340 which sets font_size. Set font_bold. Setting font variables
341 calls set_samples (thrice).
342 """
343 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
344 font_name = configured_font[0].lower()
345 font_size = configured_font[1]
346 font_bold = configured_font[2]=='bold'
347
348 # Set editor font selection list and font_name.
349 fonts = list(tkFont.families(self))
350 fonts.sort()
351 for font in fonts:
352 self.fontlist.insert(END, font)
353 self.font_name.set(font_name)
354 lc_fonts = [s.lower() for s in fonts]
355 try:
356 current_font_index = lc_fonts.index(font_name)
357 self.fontlist.see(current_font_index)
358 self.fontlist.select_set(current_font_index)
359 self.fontlist.select_anchor(current_font_index)
360 self.fontlist.activate(current_font_index)
361 except ValueError:
362 pass
363 # Set font size dropdown.
364 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
365 '16', '18', '20', '22', '25', '29', '34', '40'),
366 font_size)
367 # Set font weight.
368 self.font_bold.set(font_bold)
369
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400370 def var_changed_font(self, *params):
371 """Store changes to font attributes.
372
373 When one font attribute changes, save them all, as they are
374 not independent from each other. In particular, when we are
375 overriding the default font, we need to write out everything.
376 """
377 value = self.font_name.get()
378 changes.add_option('main', 'EditorWindow', 'font', value)
379 value = self.font_size.get()
380 changes.add_option('main', 'EditorWindow', 'font-size', value)
381 value = self.font_bold.get()
382 changes.add_option('main', 'EditorWindow', 'font-bold', value)
383 self.set_samples()
384
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400385 def on_fontlist_select(self, event):
386 """Handle selecting a font from the list.
387
388 Event can result from either mouse click or Up or Down key.
389 Set font_name and example displays to selection.
390 """
391 font = self.fontlist.get(
392 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
393 self.font_name.set(font.lower())
394
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400395 def set_samples(self, event=None):
396 """Update update both screen samples with the font settings.
397
398 Called on font initialization and change events.
399 Accesses font_name, font_size, and font_bold Variables.
400 Updates font_sample and hightlight page highlight_sample.
401 """
402 font_name = self.font_name.get()
403 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
404 new_font = (font_name, self.font_size.get(), font_weight)
405 self.font_sample['font'] = new_font
406 self.highlight_sample['font'] = new_font
407
408 def load_tab_cfg(self):
409 """Load current configuration settings for the tab options.
410
411 Attributes updated:
412 space_num: Set to value from idleConf.
413 """
414 # Set indent sizes.
415 space_num = idleConf.GetOption(
416 'main', 'Indent', 'num-spaces', default=4, type='int')
417 self.space_num.set(space_num)
418
csabellabac7d332017-06-26 17:46:26 -0400419 def create_page_highlight(self):
csabella7eb58832017-07-04 21:30:58 -0400420 """Return frame of widgets for Highlighting tab.
421
csabella36329a42017-07-13 23:32:01 -0400422 Tk Variables:
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400423 color: Color of selected target.
csabella7eb58832017-07-04 21:30:58 -0400424 builtin_theme: Menu variable for built-in theme.
425 custom_theme: Menu variable for custom theme.
426 fg_bg_toggle: Toggle for foreground/background color.
csabella36329a42017-07-13 23:32:01 -0400427 Note: this has no callback.
csabella7eb58832017-07-04 21:30:58 -0400428 is_builtin_theme: Selector for built-in or custom theme.
429 highlight_target: Menu variable for the highlight tag target.
csabella36329a42017-07-13 23:32:01 -0400430
431 Instance Data Attributes:
432 theme_elements: Dictionary of tags for text highlighting.
433 The key is the display name and the value is a tuple of
434 (tag name, display sort order).
435
436 Methods [attachment]:
437 load_theme_cfg: Load current highlight colors.
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400438 get_color: Invoke colorchooser [button_set_color].
439 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
csabella36329a42017-07-13 23:32:01 -0400440 set_highlight_target: set fg_bg_toggle, set_color_sample().
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400441 set_color_sample: Set frame background to target.
442 on_new_color_set: Set new color and add option.
csabella36329a42017-07-13 23:32:01 -0400443 paint_theme_sample: Recolor sample.
444 get_new_theme_name: Get from popup.
445 create_new_theme: Combine theme with changes and save.
446 save_as_new_theme: Save [button_save_custom_theme].
447 set_theme_type: Command for [is_builtin_theme].
448 delete_custom_theme: Ativate default [button_delete_custom_theme].
449 save_new_theme: Save to userCfg['theme'] (is function).
450
451 Widget Structure: (*) widgets bound to self
452 frame
453 frame_custom: LabelFrame
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400454 (*)highlight_sample: Text
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400455 (*)frame_color_set: Frame
456 button_set_color: Button
csabella36329a42017-07-13 23:32:01 -0400457 (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
458 frame_fg_bg_toggle: Frame
459 (*)radio_fg: Radiobutton - fg_bg_toggle
460 (*)radio_bg: Radiobutton - fg_bg_toggle
461 button_save_custom_theme: Button
462 frame_theme: LabelFrame
463 theme_type_title: Label
464 (*)radio_theme_builtin: Radiobutton - is_builtin_theme
465 (*)radio_theme_custom: Radiobutton - is_builtin_theme
466 (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
467 (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
468 (*)button_delete_custom_theme: Button
469 (*)new_custom_theme: Label
csabella7eb58832017-07-04 21:30:58 -0400470 """
csabella36329a42017-07-13 23:32:01 -0400471 self.theme_elements={
472 'Normal Text': ('normal', '00'),
473 'Python Keywords': ('keyword', '01'),
474 'Python Definitions': ('definition', '02'),
475 'Python Builtins': ('builtin', '03'),
476 'Python Comments': ('comment', '04'),
477 'Python Strings': ('string', '05'),
478 'Selected Text': ('hilite', '06'),
479 'Found Text': ('hit', '07'),
480 'Cursor': ('cursor', '08'),
481 'Editor Breakpoint': ('break', '09'),
482 'Shell Normal Text': ('console', '10'),
483 'Shell Error Text': ('error', '11'),
484 'Shell Stdout Text': ('stdout', '12'),
485 'Shell Stderr Text': ('stderr', '13'),
486 }
Terry Jan Reedy22405332014-07-30 19:24:32 -0400487 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400488 self.builtin_theme = tracers.add(
489 StringVar(parent), self.var_changed_builtin_theme)
490 self.custom_theme = tracers.add(
491 StringVar(parent), self.var_changed_custom_theme)
csabellabac7d332017-06-26 17:46:26 -0400492 self.fg_bg_toggle = BooleanVar(parent)
csabella5b591542017-07-28 14:40:59 -0400493 self.color = tracers.add(
494 StringVar(parent), self.var_changed_color)
495 self.is_builtin_theme = tracers.add(
496 BooleanVar(parent), self.var_changed_is_builtin_theme)
497 self.highlight_target = tracers.add(
498 StringVar(parent), self.var_changed_highlight_target)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400499
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400500 # Widget creation:
501 # body frame and section frames
502 frame = Frame(self.note)
csabellabac7d332017-06-26 17:46:26 -0400503 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400504 text=' Custom Highlighting ')
csabellabac7d332017-06-26 17:46:26 -0400505 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400506 text=' Highlighting Theme ')
csabellabac7d332017-06-26 17:46:26 -0400507 #frame_custom
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400508 self.highlight_sample=Text(
csabellabac7d332017-06-26 17:46:26 -0400509 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400510 font=('courier', 12, ''), cursor='hand2', width=21, height=13,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400511 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400512 text=self.highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400513 text.bind('<Double-Button-1>', lambda e: 'break')
514 text.bind('<B1-Motion>', lambda e: 'break')
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400515 text_and_tags=(('\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400516 ('#you can click here', 'comment'), ('\n', 'normal'),
517 ('#to choose items', 'comment'), ('\n', 'normal'),
518 ('def', 'keyword'), (' ', 'normal'),
519 ('func', 'definition'), ('(param):\n ', 'normal'),
520 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
521 ("'string'", 'string'), ('\n var1 = ', 'normal'),
522 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
523 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
524 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400525 ('None', 'keyword'), (')\n', 'normal'),
526 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400527 (' error ', 'error'), (' ', 'normal'),
528 ('cursor |', 'cursor'), ('\n ', 'normal'),
529 ('shell', 'console'), (' ', 'normal'),
530 ('stdout', 'stdout'), (' ', 'normal'),
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400531 ('stderr', 'stderr'), ('\n\n', 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400532 for texttag in text_and_tags:
533 text.insert(END, texttag[0], texttag[1])
534 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400535 def tem(event, elem=element):
csabellabac7d332017-06-26 17:46:26 -0400536 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400537 text.tag_bind(
csabellabac7d332017-06-26 17:46:26 -0400538 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400539 text['state'] = DISABLED
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400540 self.frame_color_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
csabellabac7d332017-06-26 17:46:26 -0400541 frame_fg_bg_toggle = Frame(frame_custom)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400542 button_set_color = Button(
543 self.frame_color_set, text='Choose Color for :',
544 command=self.get_color, highlightthickness=0)
csabellabac7d332017-06-26 17:46:26 -0400545 self.opt_menu_highlight_target = DynOptionMenu(
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400546 self.frame_color_set, self.highlight_target, None,
csabellabac7d332017-06-26 17:46:26 -0400547 highlightthickness=0) #, command=self.set_highlight_targetBinding
548 self.radio_fg = Radiobutton(
549 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400550 text='Foreground', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400551 self.radio_bg=Radiobutton(
552 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400553 text='Background', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400554 self.fg_bg_toggle.set(1)
555 button_save_custom_theme = Button(
556 frame_custom, text='Save as New Custom Theme',
557 command=self.save_as_new_theme)
558 #frame_theme
559 theme_type_title = Label(frame_theme, text='Select : ')
560 self.radio_theme_builtin = Radiobutton(
561 frame_theme, variable=self.is_builtin_theme, value=1,
562 command=self.set_theme_type, text='a Built-in Theme')
563 self.radio_theme_custom = Radiobutton(
564 frame_theme, variable=self.is_builtin_theme, value=0,
565 command=self.set_theme_type, text='a Custom Theme')
566 self.opt_menu_theme_builtin = DynOptionMenu(
567 frame_theme, self.builtin_theme, None, command=None)
568 self.opt_menu_theme_custom=DynOptionMenu(
569 frame_theme, self.custom_theme, None, command=None)
570 self.button_delete_custom_theme=Button(
571 frame_theme, text='Delete Custom Theme',
572 command=self.delete_custom_theme)
573 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400574
Steven M. Gava952d0a52001-08-03 04:43:44 +0000575 ##widget packing
576 #body
csabellabac7d332017-06-26 17:46:26 -0400577 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
578 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
579 #frame_custom
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400580 self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400581 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400582 self.highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400583 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400584 button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
csabellabac7d332017-06-26 17:46:26 -0400585 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400586 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
csabellabac7d332017-06-26 17:46:26 -0400587 self.radio_fg.pack(side=LEFT, anchor=E)
588 self.radio_bg.pack(side=RIGHT, anchor=W)
589 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
590 #frame_theme
591 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
592 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
593 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
594 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
595 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
596 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500597 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000598 return frame
599
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400600 def load_theme_cfg(self):
601 """Load current configuration settings for the theme options.
602
603 Based on the is_builtin_theme toggle, the theme is set as
604 either builtin or custom and the initial widget values
605 reflect the current settings from idleConf.
606
607 Attributes updated:
608 is_builtin_theme: Set from idleConf.
609 opt_menu_theme_builtin: List of default themes from idleConf.
610 opt_menu_theme_custom: List of custom themes from idleConf.
611 radio_theme_custom: Disabled if there are no custom themes.
612 custom_theme: Message with additional information.
613 opt_menu_highlight_target: Create menu from self.theme_elements.
614
615 Methods:
616 set_theme_type
617 paint_theme_sample
618 set_highlight_target
619 """
620 # Set current theme type radiobutton.
621 self.is_builtin_theme.set(idleConf.GetOption(
622 'main', 'Theme', 'default', type='bool', default=1))
623 # Set current theme.
624 current_option = idleConf.CurrentTheme()
625 # Load available theme option menus.
626 if self.is_builtin_theme.get(): # Default theme selected.
627 item_list = idleConf.GetSectionList('default', 'highlight')
628 item_list.sort()
629 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
630 item_list = idleConf.GetSectionList('user', 'highlight')
631 item_list.sort()
632 if not item_list:
633 self.radio_theme_custom['state'] = DISABLED
634 self.custom_theme.set('- no custom themes -')
635 else:
636 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
637 else: # User theme selected.
638 item_list = idleConf.GetSectionList('user', 'highlight')
639 item_list.sort()
640 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
641 item_list = idleConf.GetSectionList('default', 'highlight')
642 item_list.sort()
643 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
644 self.set_theme_type()
645 # Load theme element option menu.
646 theme_names = list(self.theme_elements.keys())
647 theme_names.sort(key=lambda x: self.theme_elements[x][1])
648 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
649 self.paint_theme_sample()
650 self.set_highlight_target()
651
652 def var_changed_builtin_theme(self, *params):
653 """Process new builtin theme selection.
654
655 Add the changed theme's name to the changed_items and recreate
656 the sample with the values from the selected theme.
657 """
658 old_themes = ('IDLE Classic', 'IDLE New')
659 value = self.builtin_theme.get()
660 if value not in old_themes:
661 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
662 changes.add_option('main', 'Theme', 'name', old_themes[0])
663 changes.add_option('main', 'Theme', 'name2', value)
664 self.new_custom_theme.config(text='New theme, see Help',
665 fg='#500000')
666 else:
667 changes.add_option('main', 'Theme', 'name', value)
668 changes.add_option('main', 'Theme', 'name2', '')
669 self.new_custom_theme.config(text='', fg='black')
670 self.paint_theme_sample()
671
672 def var_changed_custom_theme(self, *params):
673 """Process new custom theme selection.
674
675 If a new custom theme is selected, add the name to the
676 changed_items and apply the theme to the sample.
677 """
678 value = self.custom_theme.get()
679 if value != '- no custom themes -':
680 changes.add_option('main', 'Theme', 'name', value)
681 self.paint_theme_sample()
682
683 def var_changed_is_builtin_theme(self, *params):
684 """Process toggle between builtin and custom theme.
685
686 Update the default toggle value and apply the newly
687 selected theme type.
688 """
689 value = self.is_builtin_theme.get()
690 changes.add_option('main', 'Theme', 'default', value)
691 if value:
692 self.var_changed_builtin_theme()
693 else:
694 self.var_changed_custom_theme()
695
696 def var_changed_color(self, *params):
697 "Process change to color choice."
698 self.on_new_color_set()
699
700 def var_changed_highlight_target(self, *params):
701 "Process selection of new target tag for highlighting."
702 self.set_highlight_target()
703
704 def set_theme_type(self):
705 """Set available screen options based on builtin or custom theme.
706
707 Attributes accessed:
708 is_builtin_theme
709
710 Attributes updated:
711 opt_menu_theme_builtin
712 opt_menu_theme_custom
713 button_delete_custom_theme
714 radio_theme_custom
715
716 Called from:
717 handler for radio_theme_builtin and radio_theme_custom
718 delete_custom_theme
719 create_new_theme
720 load_theme_cfg
721 """
722 if self.is_builtin_theme.get():
723 self.opt_menu_theme_builtin['state'] = NORMAL
724 self.opt_menu_theme_custom['state'] = DISABLED
725 self.button_delete_custom_theme['state'] = DISABLED
726 else:
727 self.opt_menu_theme_builtin['state'] = DISABLED
728 self.radio_theme_custom['state'] = NORMAL
729 self.opt_menu_theme_custom['state'] = NORMAL
730 self.button_delete_custom_theme['state'] = NORMAL
731
732 def get_color(self):
733 """Handle button to select a new color for the target tag.
734
735 If a new color is selected while using a builtin theme, a
736 name must be supplied to create a custom theme.
737
738 Attributes accessed:
739 highlight_target
740 frame_color_set
741 is_builtin_theme
742
743 Attributes updated:
744 color
745
746 Methods:
747 get_new_theme_name
748 create_new_theme
749 """
750 target = self.highlight_target.get()
751 prev_color = self.frame_color_set.cget('bg')
752 rgbTuplet, color_string = tkColorChooser.askcolor(
753 parent=self, title='Pick new color for : '+target,
754 initialcolor=prev_color)
755 if color_string and (color_string != prev_color):
756 # User didn't cancel and they chose a new color.
757 if self.is_builtin_theme.get(): # Current theme is a built-in.
758 message = ('Your changes will be saved as a new Custom Theme. '
759 'Enter a name for your new Custom Theme below.')
760 new_theme = self.get_new_theme_name(message)
761 if not new_theme: # User cancelled custom theme creation.
762 return
763 else: # Create new custom theme based on previously active theme.
764 self.create_new_theme(new_theme)
765 self.color.set(color_string)
766 else: # Current theme is user defined.
767 self.color.set(color_string)
768
769 def on_new_color_set(self):
770 "Display sample of new color selection on the dialog."
771 new_color=self.color.get()
772 self.frame_color_set.config(bg=new_color) # Set sample.
773 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
774 sample_element = self.theme_elements[self.highlight_target.get()][0]
775 self.highlight_sample.tag_config(sample_element, **{plane:new_color})
776 theme = self.custom_theme.get()
777 theme_element = sample_element + '-' + plane
778 changes.add_option('highlight', theme, theme_element, new_color)
779
780 def get_new_theme_name(self, message):
781 "Return name of new theme from query popup."
782 used_names = (idleConf.GetSectionList('user', 'highlight') +
783 idleConf.GetSectionList('default', 'highlight'))
784 new_theme = SectionName(
785 self, 'New Custom Theme', message, used_names).result
786 return new_theme
787
788 def save_as_new_theme(self):
789 """Prompt for new theme name and create the theme.
790
791 Methods:
792 get_new_theme_name
793 create_new_theme
794 """
795 new_theme_name = self.get_new_theme_name('New Theme Name:')
796 if new_theme_name:
797 self.create_new_theme(new_theme_name)
798
799 def create_new_theme(self, new_theme_name):
800 """Create a new custom theme with the given name.
801
802 Create the new theme based on the previously active theme
803 with the current changes applied. Once it is saved, then
804 activate the new theme.
805
806 Attributes accessed:
807 builtin_theme
808 custom_theme
809
810 Attributes updated:
811 opt_menu_theme_custom
812 is_builtin_theme
813
814 Method:
815 save_new_theme
816 set_theme_type
817 """
818 if self.is_builtin_theme.get():
819 theme_type = 'default'
820 theme_name = self.builtin_theme.get()
821 else:
822 theme_type = 'user'
823 theme_name = self.custom_theme.get()
824 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
825 # Apply any of the old theme's unsaved changes to the new theme.
826 if theme_name in changes['highlight']:
827 theme_changes = changes['highlight'][theme_name]
828 for element in theme_changes:
829 new_theme[element] = theme_changes[element]
830 # Save the new theme.
831 self.save_new_theme(new_theme_name, new_theme)
832 # Change GUI over to the new theme.
833 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
834 custom_theme_list.sort()
835 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
836 self.is_builtin_theme.set(0)
837 self.set_theme_type()
838
839 def set_highlight_target(self):
840 """Set fg/bg toggle and color based on highlight tag target.
841
842 Instance variables accessed:
843 highlight_target
844
845 Attributes updated:
846 radio_fg
847 radio_bg
848 fg_bg_toggle
849
850 Methods:
851 set_color_sample
852
853 Called from:
854 var_changed_highlight_target
855 load_theme_cfg
856 """
857 if self.highlight_target.get() == 'Cursor': # bg not possible
858 self.radio_fg['state'] = DISABLED
859 self.radio_bg['state'] = DISABLED
860 self.fg_bg_toggle.set(1)
861 else: # Both fg and bg can be set.
862 self.radio_fg['state'] = NORMAL
863 self.radio_bg['state'] = NORMAL
864 self.fg_bg_toggle.set(1)
865 self.set_color_sample()
866
867 def set_color_sample_binding(self, *args):
868 """Change color sample based on foreground/background toggle.
869
870 Methods:
871 set_color_sample
872 """
873 self.set_color_sample()
874
875 def set_color_sample(self):
876 """Set the color of the frame background to reflect the selected target.
877
878 Instance variables accessed:
879 theme_elements
880 highlight_target
881 fg_bg_toggle
882 highlight_sample
883
884 Attributes updated:
885 frame_color_set
886 """
887 # Set the color sample area.
888 tag = self.theme_elements[self.highlight_target.get()][0]
889 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
890 color = self.highlight_sample.tag_cget(tag, plane)
891 self.frame_color_set.config(bg=color)
892
893 def paint_theme_sample(self):
894 """Apply the theme colors to each element tag in the sample text.
895
896 Instance attributes accessed:
897 theme_elements
898 is_builtin_theme
899 builtin_theme
900 custom_theme
901
902 Attributes updated:
903 highlight_sample: Set the tag elements to the theme.
904
905 Methods:
906 set_color_sample
907
908 Called from:
909 var_changed_builtin_theme
910 var_changed_custom_theme
911 load_theme_cfg
912 """
913 if self.is_builtin_theme.get(): # Default theme
914 theme = self.builtin_theme.get()
915 else: # User theme
916 theme = self.custom_theme.get()
917 for element_title in self.theme_elements:
918 element = self.theme_elements[element_title][0]
919 colors = idleConf.GetHighlight(theme, element)
920 if element == 'cursor': # Cursor sample needs special painting.
921 colors['background'] = idleConf.GetHighlight(
922 theme, 'normal', fgBg='bg')
923 # Handle any unsaved changes to this theme.
924 if theme in changes['highlight']:
925 theme_dict = changes['highlight'][theme]
926 if element + '-foreground' in theme_dict:
927 colors['foreground'] = theme_dict[element + '-foreground']
928 if element + '-background' in theme_dict:
929 colors['background'] = theme_dict[element + '-background']
930 self.highlight_sample.tag_config(element, **colors)
931 self.set_color_sample()
932
933 def save_new_theme(self, theme_name, theme):
934 """Save a newly created theme to idleConf.
935
936 theme_name - string, the name of the new theme
937 theme - dictionary containing the new theme
938 """
939 if not idleConf.userCfg['highlight'].has_section(theme_name):
940 idleConf.userCfg['highlight'].add_section(theme_name)
941 for element in theme:
942 value = theme[element]
943 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
944
945 def delete_custom_theme(self):
946 """Handle event to delete custom theme.
947
948 The current theme is deactivated and the default theme is
949 activated. The custom theme is permanently removed from
950 the config file.
951
952 Attributes accessed:
953 custom_theme
954
955 Attributes updated:
956 radio_theme_custom
957 opt_menu_theme_custom
958 is_builtin_theme
959 builtin_theme
960
961 Methods:
962 deactivate_current_config
963 save_all_changed_extensions
964 activate_config_changes
965 set_theme_type
966 """
967 theme_name = self.custom_theme.get()
968 delmsg = 'Are you sure you wish to delete the theme %r ?'
969 if not tkMessageBox.askyesno(
970 'Delete Theme', delmsg % theme_name, parent=self):
971 return
972 self.deactivate_current_config()
973 # Remove theme from changes, config, and file.
974 changes.delete_section('highlight', theme_name)
975 # Reload user theme list.
976 item_list = idleConf.GetSectionList('user', 'highlight')
977 item_list.sort()
978 if not item_list:
979 self.radio_theme_custom['state'] = DISABLED
980 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
981 else:
982 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
983 # Revert to default theme.
984 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
985 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
986 # User can't back out of these changes, they must be applied now.
987 changes.save_all()
988 self.save_all_changed_extensions()
989 self.activate_config_changes()
990 self.set_theme_type()
991
992
csabellabac7d332017-06-26 17:46:26 -0400993 def create_page_keys(self):
csabella7eb58832017-07-04 21:30:58 -0400994 """Return frame of widgets for Keys tab.
995
csabella36329a42017-07-13 23:32:01 -0400996 Tk Variables:
csabella7eb58832017-07-04 21:30:58 -0400997 builtin_keys: Menu variable for built-in keybindings.
998 custom_keys: Menu variable for custom keybindings.
999 are_keys_builtin: Selector for built-in or custom keybindings.
1000 keybinding: Action/key bindings.
csabella36329a42017-07-13 23:32:01 -04001001
1002 Methods:
1003 load_key_config: Set table.
1004 load_keys_list: Reload active set.
1005 keybinding_selected: Bound to list_bindings button release.
1006 get_new_keys: Command for button_new_keys.
1007 get_new_keys_name: Call popup.
1008 create_new_key_set: Combine active keyset and changes.
1009 set_keys_type: Command for are_keys_builtin.
1010 delete_custom_keys: Command for button_delete_custom_keys.
1011 save_as_new_key_set: Command for button_save_custom_keys.
1012 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1013 deactivate_current_config: Remove keys bindings in editors.
1014
1015 Widget Structure: (*) widgets bound to self
1016 frame
1017 frame_custom: LabelFrame
1018 frame_target: Frame
1019 target_title: Label
1020 scroll_target_y: Scrollbar
1021 scroll_target_x: Scrollbar
1022 (*)list_bindings: ListBox
1023 (*)button_new_keys: Button
1024 frame_key_sets: LabelFrame
1025 frames[0]: Frame
1026 (*)radio_keys_builtin: Radiobutton - are_keys_builtin
1027 (*)radio_keys_custom: Radiobutton - are_keys_builtin
1028 (*)opt_menu_keys_builtin: DynOptionMenu - builtin_keys
1029 (*)opt_menu_keys_custom: DynOptionMenu - custom_keys
1030 (*)new_custom_keys: Label
1031 frames[1]: Frame
1032 (*)button_delete_custom_keys: Button
1033 button_save_custom_keys: Button
csabella7eb58832017-07-04 21:30:58 -04001034 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001035 parent = self.parent
csabella5b591542017-07-28 14:40:59 -04001036 self.builtin_keys = tracers.add(
1037 StringVar(parent), self.var_changed_builtin_keys)
1038 self.custom_keys = tracers.add(
1039 StringVar(parent), self.var_changed_custom_keys)
1040 self.are_keys_builtin = tracers.add(
1041 BooleanVar(parent), self.var_changed_are_keys_builtin)
1042 self.keybinding = tracers.add(
1043 StringVar(parent), self.var_changed_keybinding)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001044
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001045 # Widget creation:
1046 # body and section frames.
1047 frame = Frame(self.note)
csabellabac7d332017-06-26 17:46:26 -04001048 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001049 frame, borderwidth=2, relief=GROOVE,
1050 text=' Custom Key Bindings ')
csabellabac7d332017-06-26 17:46:26 -04001051 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001052 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
csabellabac7d332017-06-26 17:46:26 -04001053 #frame_custom
1054 frame_target = Frame(frame_custom)
1055 target_title = Label(frame_target, text='Action - Key(s)')
1056 scroll_target_y = Scrollbar(frame_target)
1057 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1058 self.list_bindings = Listbox(
1059 frame_target, takefocus=FALSE, exportselection=FALSE)
1060 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
1061 scroll_target_y.config(command=self.list_bindings.yview)
1062 scroll_target_x.config(command=self.list_bindings.xview)
1063 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
1064 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
1065 self.button_new_keys = Button(
1066 frame_custom, text='Get New Keys for Selection',
1067 command=self.get_new_keys, state=DISABLED)
1068 #frame_key_sets
1069 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +00001070 for i in range(2)]
csabellabac7d332017-06-26 17:46:26 -04001071 self.radio_keys_builtin = Radiobutton(
1072 frames[0], variable=self.are_keys_builtin, value=1,
1073 command=self.set_keys_type, text='Use a Built-in Key Set')
1074 self.radio_keys_custom = Radiobutton(
1075 frames[0], variable=self.are_keys_builtin, value=0,
1076 command=self.set_keys_type, text='Use a Custom Key Set')
1077 self.opt_menu_keys_builtin = DynOptionMenu(
1078 frames[0], self.builtin_keys, None, command=None)
1079 self.opt_menu_keys_custom = DynOptionMenu(
1080 frames[0], self.custom_keys, None, command=None)
1081 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001082 frames[1], text='Delete Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001083 command=self.delete_custom_keys)
1084 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001085 frames[1], text='Save as New Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001086 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001087 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001088
Steven M. Gava60fc7072001-08-04 13:58:22 +00001089 ##widget packing
1090 #body
csabellabac7d332017-06-26 17:46:26 -04001091 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1092 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1093 #frame_custom
1094 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1095 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +00001096 #frame target
csabellabac7d332017-06-26 17:46:26 -04001097 frame_target.columnconfigure(0, weight=1)
1098 frame_target.rowconfigure(1, weight=1)
1099 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1100 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
1101 scroll_target_y.grid(row=1, column=1, sticky=NS)
1102 scroll_target_x.grid(row=2, column=0, sticky=EW)
1103 #frame_key_sets
1104 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
1105 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
1106 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
1107 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001108 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001109 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1110 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +00001111 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1112 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +00001113 return frame
1114
Terry Jan Reedyb1660802017-07-27 18:28:01 -04001115 def load_key_cfg(self):
1116 "Load current configuration settings for the keybinding options."
1117 # Set current keys type radiobutton.
1118 self.are_keys_builtin.set(idleConf.GetOption(
1119 'main', 'Keys', 'default', type='bool', default=1))
1120 # Set current keys.
1121 current_option = idleConf.CurrentKeys()
1122 # Load available keyset option menus.
1123 if self.are_keys_builtin.get(): # Default theme selected.
1124 item_list = idleConf.GetSectionList('default', 'keys')
1125 item_list.sort()
1126 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1127 item_list = idleConf.GetSectionList('user', 'keys')
1128 item_list.sort()
1129 if not item_list:
1130 self.radio_keys_custom['state'] = DISABLED
1131 self.custom_keys.set('- no custom keys -')
1132 else:
1133 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1134 else: # User key set selected.
1135 item_list = idleConf.GetSectionList('user', 'keys')
1136 item_list.sort()
1137 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1138 item_list = idleConf.GetSectionList('default', 'keys')
1139 item_list.sort()
1140 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1141 self.set_keys_type()
1142 # Load keyset element list.
1143 keyset_name = idleConf.CurrentKeys()
1144 self.load_keys_list(keyset_name)
1145
Terry Jan Reedyb1660802017-07-27 18:28:01 -04001146 def var_changed_builtin_keys(self, *params):
1147 "Process selection of builtin key set."
1148 old_keys = (
1149 'IDLE Classic Windows',
1150 'IDLE Classic Unix',
1151 'IDLE Classic Mac',
1152 'IDLE Classic OSX',
1153 )
1154 value = self.builtin_keys.get()
1155 if value not in old_keys:
1156 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1157 changes.add_option('main', 'Keys', 'name', old_keys[0])
1158 changes.add_option('main', 'Keys', 'name2', value)
1159 self.new_custom_keys.config(text='New key set, see Help',
1160 fg='#500000')
1161 else:
1162 changes.add_option('main', 'Keys', 'name', value)
1163 changes.add_option('main', 'Keys', 'name2', '')
1164 self.new_custom_keys.config(text='', fg='black')
1165 self.load_keys_list(value)
1166
1167 def var_changed_custom_keys(self, *params):
1168 "Process selection of custom key set."
1169 value = self.custom_keys.get()
1170 if value != '- no custom keys -':
1171 changes.add_option('main', 'Keys', 'name', value)
1172 self.load_keys_list(value)
1173
1174 def var_changed_are_keys_builtin(self, *params):
1175 "Process toggle between builtin key set and custom key set."
1176 value = self.are_keys_builtin.get()
1177 changes.add_option('main', 'Keys', 'default', value)
1178 if value:
1179 self.var_changed_builtin_keys()
1180 else:
1181 self.var_changed_custom_keys()
1182
1183 def var_changed_keybinding(self, *params):
1184 "Store change to a keybinding."
1185 value = self.keybinding.get()
1186 key_set = self.custom_keys.get()
1187 event = self.list_bindings.get(ANCHOR).split()[0]
1188 if idleConf.IsCoreBinding(event):
1189 changes.add_option('keys', key_set, event, value)
1190 else: # Event is an extension binding.
1191 ext_name = idleConf.GetExtnNameForEvent(event)
1192 ext_keybind_section = ext_name + '_cfgBindings'
1193 changes.add_option('extensions', ext_keybind_section, event, value)
1194
1195 def set_keys_type(self):
1196 "Set available screen options based on builtin or custom key set."
1197 if self.are_keys_builtin.get():
1198 self.opt_menu_keys_builtin['state'] = NORMAL
1199 self.opt_menu_keys_custom['state'] = DISABLED
1200 self.button_delete_custom_keys['state'] = DISABLED
1201 else:
1202 self.opt_menu_keys_builtin['state'] = DISABLED
1203 self.radio_keys_custom['state'] = NORMAL
1204 self.opt_menu_keys_custom['state'] = NORMAL
1205 self.button_delete_custom_keys['state'] = NORMAL
1206
1207 def get_new_keys(self):
1208 """Handle event to change key binding for selected line.
1209
1210 A selection of a key/binding in the list of current
1211 bindings pops up a dialog to enter a new binding. If
1212 the current key set is builtin and a binding has
1213 changed, then a name for a custom key set needs to be
1214 entered for the change to be applied.
1215 """
1216 list_index = self.list_bindings.index(ANCHOR)
1217 binding = self.list_bindings.get(list_index)
1218 bind_name = binding.split()[0]
1219 if self.are_keys_builtin.get():
1220 current_key_set_name = self.builtin_keys.get()
1221 else:
1222 current_key_set_name = self.custom_keys.get()
1223 current_bindings = idleConf.GetCurrentKeySet()
1224 if current_key_set_name in changes['keys']: # unsaved changes
1225 key_set_changes = changes['keys'][current_key_set_name]
1226 for event in key_set_changes:
1227 current_bindings[event] = key_set_changes[event].split()
1228 current_key_sequences = list(current_bindings.values())
1229 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1230 current_key_sequences).result
1231 if new_keys:
1232 if self.are_keys_builtin.get(): # Current key set is a built-in.
1233 message = ('Your changes will be saved as a new Custom Key Set.'
1234 ' Enter a name for your new Custom Key Set below.')
1235 new_keyset = self.get_new_keys_name(message)
1236 if not new_keyset: # User cancelled custom key set creation.
1237 self.list_bindings.select_set(list_index)
1238 self.list_bindings.select_anchor(list_index)
1239 return
1240 else: # Create new custom key set based on previously active key set.
1241 self.create_new_key_set(new_keyset)
1242 self.list_bindings.delete(list_index)
1243 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
1244 self.list_bindings.select_set(list_index)
1245 self.list_bindings.select_anchor(list_index)
1246 self.keybinding.set(new_keys)
1247 else:
1248 self.list_bindings.select_set(list_index)
1249 self.list_bindings.select_anchor(list_index)
1250
1251 def get_new_keys_name(self, message):
1252 "Return new key set name from query popup."
1253 used_names = (idleConf.GetSectionList('user', 'keys') +
1254 idleConf.GetSectionList('default', 'keys'))
1255 new_keyset = SectionName(
1256 self, 'New Custom Key Set', message, used_names).result
1257 return new_keyset
1258
1259 def save_as_new_key_set(self):
1260 "Prompt for name of new key set and save changes using that name."
1261 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1262 if new_keys_name:
1263 self.create_new_key_set(new_keys_name)
1264
1265 def keybinding_selected(self, event):
1266 "Activate button to assign new keys to selected action."
1267 self.button_new_keys['state'] = NORMAL
1268
1269 def create_new_key_set(self, new_key_set_name):
1270 """Create a new custom key set with the given name.
1271
1272 Create the new key set based on the previously active set
1273 with the current changes applied. Once it is saved, then
1274 activate the new key set.
1275 """
1276 if self.are_keys_builtin.get():
1277 prev_key_set_name = self.builtin_keys.get()
1278 else:
1279 prev_key_set_name = self.custom_keys.get()
1280 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1281 new_keys = {}
1282 for event in prev_keys: # Add key set to changed items.
1283 event_name = event[2:-2] # Trim off the angle brackets.
1284 binding = ' '.join(prev_keys[event])
1285 new_keys[event_name] = binding
1286 # Handle any unsaved changes to prev key set.
1287 if prev_key_set_name in changes['keys']:
1288 key_set_changes = changes['keys'][prev_key_set_name]
1289 for event in key_set_changes:
1290 new_keys[event] = key_set_changes[event]
1291 # Save the new key set.
1292 self.save_new_key_set(new_key_set_name, new_keys)
1293 # Change GUI over to the new key set.
1294 custom_key_list = idleConf.GetSectionList('user', 'keys')
1295 custom_key_list.sort()
1296 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
1297 self.are_keys_builtin.set(0)
1298 self.set_keys_type()
1299
1300 def load_keys_list(self, keyset_name):
1301 """Reload the list of action/key binding pairs for the active key set.
1302
1303 An action/key binding can be selected to change the key binding.
1304 """
1305 reselect = 0
1306 if self.list_bindings.curselection():
1307 reselect = 1
1308 list_index = self.list_bindings.index(ANCHOR)
1309 keyset = idleConf.GetKeySet(keyset_name)
1310 bind_names = list(keyset.keys())
1311 bind_names.sort()
1312 self.list_bindings.delete(0, END)
1313 for bind_name in bind_names:
1314 key = ' '.join(keyset[bind_name])
1315 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1316 if keyset_name in changes['keys']:
1317 # Handle any unsaved changes to this key set.
1318 if bind_name in changes['keys'][keyset_name]:
1319 key = changes['keys'][keyset_name][bind_name]
1320 self.list_bindings.insert(END, bind_name+' - '+key)
1321 if reselect:
1322 self.list_bindings.see(list_index)
1323 self.list_bindings.select_set(list_index)
1324 self.list_bindings.select_anchor(list_index)
1325
1326 def save_new_key_set(self, keyset_name, keyset):
1327 """Save a newly created core key set.
1328
1329 keyset_name - string, the name of the new key set
1330 keyset - dictionary containing the new key set
1331 """
1332 if not idleConf.userCfg['keys'].has_section(keyset_name):
1333 idleConf.userCfg['keys'].add_section(keyset_name)
1334 for event in keyset:
1335 value = keyset[event]
1336 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1337
1338 def delete_custom_keys(self):
1339 """Handle event to delete a custom key set.
1340
1341 Applying the delete deactivates the current configuration and
1342 reverts to the default. The custom key set is permanently
1343 deleted from the config file.
1344 """
1345 keyset_name=self.custom_keys.get()
1346 delmsg = 'Are you sure you wish to delete the key set %r ?'
1347 if not tkMessageBox.askyesno(
1348 'Delete Key Set', delmsg % keyset_name, parent=self):
1349 return
1350 self.deactivate_current_config()
1351 # Remove key set from changes, config, and file.
1352 changes.delete_section('keys', keyset_name)
1353 # Reload user key set list.
1354 item_list = idleConf.GetSectionList('user', 'keys')
1355 item_list.sort()
1356 if not item_list:
1357 self.radio_keys_custom['state'] = DISABLED
1358 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
1359 else:
1360 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1361 # Revert to default key set.
1362 self.are_keys_builtin.set(idleConf.defaultCfg['main']
1363 .Get('Keys', 'default'))
1364 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
1365 or idleConf.default_keys())
1366 # User can't back out of these changes, they must be applied now.
1367 changes.save_all()
1368 self.save_all_changed_extensions()
1369 self.activate_config_changes()
1370 self.set_keys_type()
1371
1372 def deactivate_current_config(self):
1373 """Remove current key bindings.
1374
1375 Iterate over window instances defined in parent and remove
1376 the keybindings.
1377 """
1378 # Before a config is saved, some cleanup of current
1379 # config must be done - remove the previous keybindings.
1380 win_instances = self.parent.instance_dict.keys()
1381 for instance in win_instances:
1382 instance.RemoveKeybindings()
1383
1384 def activate_config_changes(self):
1385 """Apply configuration changes to current windows.
1386
1387 Dynamically update the current parent window instances
1388 with some of the configuration changes.
1389 """
1390 win_instances = self.parent.instance_dict.keys()
1391 for instance in win_instances:
1392 instance.ResetColorizer()
1393 instance.ResetFont()
1394 instance.set_notabs_indentwidth()
1395 instance.ApplyKeybindings()
1396 instance.reset_help_menu_entries()
1397
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001398
csabellabac7d332017-06-26 17:46:26 -04001399 def create_page_general(self):
csabella7eb58832017-07-04 21:30:58 -04001400 """Return frame of widgets for General tab.
1401
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001402 Enable users to provisionally change general options. Function
1403 load_general_cfg intializes tk variables and helplist using
1404 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1405 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1406 set var autosave. Entry boxes win_width_int and win_height_int
1407 set var win_width and win_height. Setting var_name invokes the
csabella5b591542017-07-28 14:40:59 -04001408 default callback that adds option to changes.
csabella36329a42017-07-13 23:32:01 -04001409
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001410 Helplist: load_general_cfg loads list user_helplist with
1411 name, position pairs and copies names to listbox helplist.
1412 Clicking a name invokes help_source selected. Clicking
1413 button_helplist_name invokes helplist_item_name, which also
1414 changes user_helplist. These functions all call
1415 set_add_delete_state. All but load call update_help_changes to
1416 rewrite changes['main']['HelpFiles'].
csabella36329a42017-07-13 23:32:01 -04001417
1418 Widget Structure: (*) widgets bound to self
1419 frame
1420 frame_run: LabelFrame
1421 startup_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001422 (*)startup_editor_on: Radiobutton - startup_edit
1423 (*)startup_shell_on: Radiobutton - startup_edit
csabella36329a42017-07-13 23:32:01 -04001424 frame_save: LabelFrame
1425 run_save_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001426 (*)save_ask_on: Radiobutton - autosave
1427 (*)save_auto_on: Radiobutton - autosave
csabella36329a42017-07-13 23:32:01 -04001428 frame_win_size: LabelFrame
1429 win_size_title: Label
1430 win_width_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001431 (*)win_width_int: Entry - win_width
csabella36329a42017-07-13 23:32:01 -04001432 win_height_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001433 (*)win_height_int: Entry - win_height
csabella36329a42017-07-13 23:32:01 -04001434 frame_help: LabelFrame
1435 frame_helplist: Frame
1436 frame_helplist_buttons: Frame
1437 (*)button_helplist_edit
1438 (*)button_helplist_add
1439 (*)button_helplist_remove
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001440 (*)helplist: ListBox
csabella36329a42017-07-13 23:32:01 -04001441 scroll_helplist: Scrollbar
csabella7eb58832017-07-04 21:30:58 -04001442 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001443 parent = self.parent
csabella5b591542017-07-28 14:40:59 -04001444 self.startup_edit = tracers.add(
1445 IntVar(parent), ('main', 'General', 'editor-on-startup'))
1446 self.autosave = tracers.add(
1447 IntVar(parent), ('main', 'General', 'autosave'))
1448 self.win_width = tracers.add(
1449 StringVar(parent), ('main', 'EditorWindow', 'width'))
1450 self.win_height = tracers.add(
1451 StringVar(parent), ('main', 'EditorWindow', 'height'))
Terry Jan Reedy22405332014-07-30 19:24:32 -04001452
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001453 # Create widgets:
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001454 # body and section frames.
1455 frame = Frame(self.note)
csabellabac7d332017-06-26 17:46:26 -04001456 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001457 text=' Startup Preferences ')
csabellabac7d332017-06-26 17:46:26 -04001458 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
1459 text=' autosave Preferences ')
1460 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
1461 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001462 text=' Additional Help Sources ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001463 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001464 startup_title = Label(frame_run, text='At Startup')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001465 self.startup_editor_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001466 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001467 text="Open Edit Window")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001468 self.startup_shell_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001469 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001470 text='Open Shell Window')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001471 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001472 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001473 self.save_ask_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001474 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001475 text="Prompt to Save")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001476 self.save_auto_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001477 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001478 text='No Prompt')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001479 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001480 win_size_title = Label(
1481 frame_win_size, text='Initial Window Size (in characters)')
1482 win_width_title = Label(frame_win_size, text='Width')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001483 self.win_width_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001484 frame_win_size, textvariable=self.win_width, width=3)
1485 win_height_title = Label(frame_win_size, text='Height')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001486 self.win_height_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001487 frame_win_size, textvariable=self.win_height, width=3)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001488 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001489 frame_helplist = Frame(frame_help)
1490 frame_helplist_buttons = Frame(frame_helplist)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001491 self.helplist = Listbox(
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001492 frame_helplist, height=5, takefocus=True,
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001493 exportselection=FALSE)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001494 scroll_helplist = Scrollbar(frame_helplist)
1495 scroll_helplist['command'] = self.helplist.yview
1496 self.helplist['yscrollcommand'] = scroll_helplist.set
1497 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
csabellabac7d332017-06-26 17:46:26 -04001498 self.button_helplist_edit = Button(
1499 frame_helplist_buttons, text='Edit', state=DISABLED,
1500 width=8, command=self.helplist_item_edit)
1501 self.button_helplist_add = Button(
1502 frame_helplist_buttons, text='Add',
1503 width=8, command=self.helplist_item_add)
1504 self.button_helplist_remove = Button(
1505 frame_helplist_buttons, text='Remove', state=DISABLED,
1506 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001507
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001508 # Pack widgets:
1509 # body.
csabellabac7d332017-06-26 17:46:26 -04001510 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
1511 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
1512 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
1513 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001514 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001515 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001516 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1517 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1518 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001519 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001520 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1521 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1522 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001523 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001524 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001525 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001526 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001527 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001528 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001529 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
1530 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1531 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001532 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
csabellabac7d332017-06-26 17:46:26 -04001533 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
1534 self.button_helplist_add.pack(side=TOP, anchor=W)
1535 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001536
Steven M. Gava952d0a52001-08-03 04:43:44 +00001537 return frame
1538
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001539 def load_general_cfg(self):
1540 "Load current configuration settings for the general options."
1541 # Set startup state.
1542 self.startup_edit.set(idleConf.GetOption(
1543 'main', 'General', 'editor-on-startup', default=0, type='bool'))
1544 # Set autosave state.
1545 self.autosave.set(idleConf.GetOption(
1546 'main', 'General', 'autosave', default=0, type='bool'))
1547 # Set initial window size.
1548 self.win_width.set(idleConf.GetOption(
1549 'main', 'EditorWindow', 'width', type='int'))
1550 self.win_height.set(idleConf.GetOption(
1551 'main', 'EditorWindow', 'height', type='int'))
1552 # Set additional help sources.
1553 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1554 self.helplist.delete(0, 'end')
1555 for help_item in self.user_helplist:
1556 self.helplist.insert(END, help_item[0])
1557 self.set_add_delete_state()
1558
1559 def var_changed_startup_edit(self, *params):
1560 "Store change to toggle for starting IDLE in the editor or shell."
1561 value = self.startup_edit.get()
1562 changes.add_option('main', 'General', 'editor-on-startup', value)
1563
1564 def var_changed_autosave(self, *params):
1565 "Store change to autosave."
1566 value = self.autosave.get()
1567 changes.add_option('main', 'General', 'autosave', value)
1568
1569 def var_changed_win_width(self, *params):
1570 "Store change to window width."
1571 value = self.win_width.get()
1572 changes.add_option('main', 'EditorWindow', 'width', value)
1573
1574 def var_changed_win_height(self, *params):
1575 "Store change to window height."
1576 value = self.win_height.get()
1577 changes.add_option('main', 'EditorWindow', 'height', value)
1578
1579 def help_source_selected(self, event):
1580 "Handle event for selecting additional help."
1581 self.set_add_delete_state()
1582
1583 def set_add_delete_state(self):
1584 "Toggle the state for the help list buttons based on list entries."
1585 if self.helplist.size() < 1: # No entries in list.
1586 self.button_helplist_edit['state'] = DISABLED
1587 self.button_helplist_remove['state'] = DISABLED
1588 else: # Some entries.
1589 if self.helplist.curselection(): # There currently is a selection.
1590 self.button_helplist_edit['state'] = NORMAL
1591 self.button_helplist_remove['state'] = NORMAL
1592 else: # There currently is not a selection.
1593 self.button_helplist_edit['state'] = DISABLED
1594 self.button_helplist_remove['state'] = DISABLED
1595
1596 def helplist_item_add(self):
1597 """Handle add button for the help list.
1598
1599 Query for name and location of new help sources and add
1600 them to the list.
1601 """
1602 help_source = HelpSource(self, 'New Help Source').result
1603 if help_source:
1604 self.user_helplist.append(help_source)
1605 self.helplist.insert(END, help_source[0])
1606 self.update_help_changes()
1607
1608 def helplist_item_edit(self):
1609 """Handle edit button for the help list.
1610
1611 Query with existing help source information and update
1612 config if the values are changed.
1613 """
1614 item_index = self.helplist.index(ANCHOR)
1615 help_source = self.user_helplist[item_index]
1616 new_help_source = HelpSource(
1617 self, 'Edit Help Source',
1618 menuitem=help_source[0],
1619 filepath=help_source[1],
1620 ).result
1621 if new_help_source and new_help_source != help_source:
1622 self.user_helplist[item_index] = new_help_source
1623 self.helplist.delete(item_index)
1624 self.helplist.insert(item_index, new_help_source[0])
1625 self.update_help_changes()
1626 self.set_add_delete_state() # Selected will be un-selected
1627
1628 def helplist_item_remove(self):
1629 """Handle remove button for the help list.
1630
1631 Delete the help list item from config.
1632 """
1633 item_index = self.helplist.index(ANCHOR)
1634 del(self.user_helplist[item_index])
1635 self.helplist.delete(item_index)
1636 self.update_help_changes()
1637 self.set_add_delete_state()
1638
1639 def update_help_changes(self):
1640 "Clear and rebuild the HelpFiles section in changes"
1641 changes['main']['HelpFiles'] = {}
1642 for num in range(1, len(self.user_helplist) + 1):
1643 changes.add_option(
1644 'main', 'HelpFiles', str(num),
1645 ';'.join(self.user_helplist[num-1][:2]))
1646
1647
csabellabac7d332017-06-26 17:46:26 -04001648 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001649 """Part of the config dialog used for configuring IDLE extensions.
1650
1651 This code is generic - it works for any and all IDLE extensions.
1652
1653 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001654 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001655 GUI interface to change the configuration values, and saves the
1656 changes using idleConf.
1657
1658 Not all changes take effect immediately - some may require restarting IDLE.
1659 This depends on each extension's implementation.
1660
1661 All values are treated as text, and it is up to the user to supply
1662 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001663 which are boolean, and can be toggled with a True/False button.
csabella36329a42017-07-13 23:32:01 -04001664
1665 Methods:
1666 load_extentions:
1667 extension_selected: Handle selection from list.
1668 create_extension_frame: Hold widgets for one extension.
1669 set_extension_value: Set in userCfg['extensions'].
1670 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001671 """
1672 parent = self.parent
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001673 frame = Frame(self.note)
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001674 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1675 self.ext_userCfg = idleConf.userCfg['extensions']
1676 self.is_int = self.register(is_int)
1677 self.load_extensions()
csabella7eb58832017-07-04 21:30:58 -04001678 # Create widgets - a listbox shows all available extensions, with the
1679 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001680 self.extension_names = StringVar(self)
1681 frame.rowconfigure(0, weight=1)
1682 frame.columnconfigure(2, weight=1)
1683 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1684 selectmode='browse')
1685 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1686 scroll = Scrollbar(frame, command=self.extension_list.yview)
1687 self.extension_list.yscrollcommand=scroll.set
1688 self.details_frame = LabelFrame(frame, width=250, height=250)
1689 self.extension_list.grid(column=0, row=0, sticky='nws')
1690 scroll.grid(column=1, row=0, sticky='ns')
1691 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1692 frame.configure(padx=10, pady=10)
1693 self.config_frame = {}
1694 self.current_extension = None
1695
1696 self.outerframe = self # TEMPORARY
1697 self.tabbed_page_set = self.extension_list # TEMPORARY
1698
csabella7eb58832017-07-04 21:30:58 -04001699 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001700 ext_names = ''
1701 for ext_name in sorted(self.extensions):
1702 self.create_extension_frame(ext_name)
1703 ext_names = ext_names + '{' + ext_name + '} '
1704 self.extension_names.set(ext_names)
1705 self.extension_list.selection_set(0)
1706 self.extension_selected(None)
1707
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001708 return frame
1709
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001710 def load_extensions(self):
1711 "Fill self.extensions with data from the default and user configs."
1712 self.extensions = {}
1713 for ext_name in idleConf.GetExtensions(active_only=False):
1714 self.extensions[ext_name] = []
1715
1716 for ext_name in self.extensions:
1717 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1718
csabella7eb58832017-07-04 21:30:58 -04001719 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001720 enables = [opt_name for opt_name in opt_list
1721 if opt_name.startswith('enable')]
1722 for opt_name in enables:
1723 opt_list.remove(opt_name)
1724 opt_list = enables + opt_list
1725
1726 for opt_name in opt_list:
1727 def_str = self.ext_defaultCfg.Get(
1728 ext_name, opt_name, raw=True)
1729 try:
1730 def_obj = {'True':True, 'False':False}[def_str]
1731 opt_type = 'bool'
1732 except KeyError:
1733 try:
1734 def_obj = int(def_str)
1735 opt_type = 'int'
1736 except ValueError:
1737 def_obj = def_str
1738 opt_type = None
1739 try:
1740 value = self.ext_userCfg.Get(
1741 ext_name, opt_name, type=opt_type, raw=True,
1742 default=def_obj)
csabella7eb58832017-07-04 21:30:58 -04001743 except ValueError: # Need this until .Get fixed.
1744 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001745 var = StringVar(self)
1746 var.set(str(value))
1747
1748 self.extensions[ext_name].append({'name': opt_name,
1749 'type': opt_type,
1750 'default': def_str,
1751 'value': value,
1752 'var': var,
1753 })
1754
1755 def extension_selected(self, event):
csabella7eb58832017-07-04 21:30:58 -04001756 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001757 newsel = self.extension_list.curselection()
1758 if newsel:
1759 newsel = self.extension_list.get(newsel)
1760 if newsel is None or newsel != self.current_extension:
1761 if self.current_extension:
1762 self.details_frame.config(text='')
1763 self.config_frame[self.current_extension].grid_forget()
1764 self.current_extension = None
1765 if newsel:
1766 self.details_frame.config(text=newsel)
1767 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1768 self.current_extension = newsel
1769
1770 def create_extension_frame(self, ext_name):
1771 """Create a frame holding the widgets to configure one extension"""
1772 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1773 self.config_frame[ext_name] = f
1774 entry_area = f.interior
csabella7eb58832017-07-04 21:30:58 -04001775 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001776 for row, opt in enumerate(self.extensions[ext_name]):
csabella7eb58832017-07-04 21:30:58 -04001777 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001778 label = Label(entry_area, text=opt['name'])
1779 label.grid(row=row, column=0, sticky=NW)
1780 var = opt['var']
1781 if opt['type'] == 'bool':
1782 Checkbutton(entry_area, textvariable=var, variable=var,
1783 onvalue='True', offvalue='False',
1784 indicatoron=FALSE, selectcolor='', width=8
1785 ).grid(row=row, column=1, sticky=W, padx=7)
1786 elif opt['type'] == 'int':
1787 Entry(entry_area, textvariable=var, validate='key',
1788 validatecommand=(self.is_int, '%P')
1789 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1790
1791 else:
1792 Entry(entry_area, textvariable=var
1793 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1794 return
1795
1796 def set_extension_value(self, section, opt):
csabella7eb58832017-07-04 21:30:58 -04001797 """Return True if the configuration was added or changed.
1798
1799 If the value is the same as the default, then remove it
1800 from user config file.
1801 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001802 name = opt['name']
1803 default = opt['default']
1804 value = opt['var'].get().strip() or default
1805 opt['var'].set(value)
1806 # if self.defaultCfg.has_section(section):
csabella7eb58832017-07-04 21:30:58 -04001807 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001808 if (value == default):
1809 return self.ext_userCfg.RemoveOption(section, name)
csabella7eb58832017-07-04 21:30:58 -04001810 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001811 return self.ext_userCfg.SetOption(section, name, value)
1812
1813 def save_all_changed_extensions(self):
csabella36329a42017-07-13 23:32:01 -04001814 """Save configuration changes to the user config file.
1815
1816 Attributes accessed:
1817 extensions
1818
1819 Methods:
1820 set_extension_value
1821 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001822 has_changes = False
1823 for ext_name in self.extensions:
1824 options = self.extensions[ext_name]
1825 for opt in options:
1826 if self.set_extension_value(ext_name, opt):
1827 has_changes = True
1828 if has_changes:
1829 self.ext_userCfg.Save()
1830
1831
csabella45bf7232017-07-26 19:09:58 -04001832class VarTrace:
1833 """Maintain Tk variables trace state."""
1834
1835 def __init__(self):
1836 """Store Tk variables and callbacks.
1837
1838 untraced: List of tuples (var, callback)
1839 that do not have the callback attached
1840 to the Tk var.
1841 traced: List of tuples (var, callback) where
1842 that callback has been attached to the var.
1843 """
1844 self.untraced = []
1845 self.traced = []
1846
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04001847 def clear(self):
1848 "Clear lists (for tests)."
1849 self.untraced.clear()
1850 self.traced.clear()
1851
csabella45bf7232017-07-26 19:09:58 -04001852 def add(self, var, callback):
1853 """Add (var, callback) tuple to untraced list.
1854
1855 Args:
1856 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04001857 callback: Either function name to be used as a callback
1858 or a tuple with IdleConf config-type, section, and
1859 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04001860
1861 Return:
1862 Tk variable instance.
1863 """
1864 if isinstance(callback, tuple):
1865 callback = self.make_callback(var, callback)
1866 self.untraced.append((var, callback))
1867 return var
1868
1869 @staticmethod
1870 def make_callback(var, config):
1871 "Return default callback function to add values to changes instance."
1872 def default_callback(*params):
1873 "Add config values to changes instance."
1874 changes.add_option(*config, var.get())
1875 return default_callback
1876
1877 def attach(self):
1878 "Attach callback to all vars that are not traced."
1879 while self.untraced:
1880 var, callback = self.untraced.pop()
1881 var.trace_add('write', callback)
1882 self.traced.append((var, callback))
1883
1884 def detach(self):
1885 "Remove callback from traced vars."
1886 while self.traced:
1887 var, callback = self.traced.pop()
1888 var.trace_remove('write', var.trace_info()[0][1])
1889 self.untraced.append((var, callback))
1890
1891
csabella5b591542017-07-28 14:40:59 -04001892tracers = VarTrace()
1893
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001894help_common = '''\
1895When you click either the Apply or Ok buttons, settings in this
1896dialog that are different from IDLE's default are saved in
1897a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001898these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001899machine. Some do not take affect until IDLE is restarted.
1900[Cancel] only cancels changes made since the last save.
1901'''
1902help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001903 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001904Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001905The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001906be used with older IDLE releases if it is saved as a custom
1907theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001908''',
1909 'Keys': '''
1910Keys:
1911The IDLE Modern Unix key set is new in June 2016. It can only
1912be used with older IDLE releases if it is saved as a custom
1913key set, with a different name.
1914''',
wohlgangerfae2c352017-06-27 21:36:23 -05001915 'Extensions': '''
1916Extensions:
1917
1918Autocomplete: Popupwait is milleseconds to wait after key char, without
1919cursor movement, before popping up completion box. Key char is '.' after
1920identifier or a '/' (or '\\' on Windows) within a string.
1921
1922FormatParagraph: Max-width is max chars in lines after re-formatting.
1923Use with paragraphs in both strings and comment blocks.
1924
1925ParenMatch: Style indicates what is highlighted when closer is entered:
1926'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1927'expression' (default) - also everything in between. Flash-delay is how
1928long to highlight if cursor is not moved (0 means forever).
1929'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001930}
1931
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001932
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001933def is_int(s):
1934 "Return 's is blank or represents an int'"
1935 if not s:
1936 return True
1937 try:
1938 int(s)
1939 return True
1940 except ValueError:
1941 return False
1942
1943
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001944class VerticalScrolledFrame(Frame):
1945 """A pure Tkinter vertically scrollable frame.
1946
1947 * Use the 'interior' attribute to place widgets inside the scrollable frame
1948 * Construct and pack/place/grid normally
1949 * This frame only allows vertical scrolling
1950 """
1951 def __init__(self, parent, *args, **kw):
1952 Frame.__init__(self, parent, *args, **kw)
1953
csabella7eb58832017-07-04 21:30:58 -04001954 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001955 vscrollbar = Scrollbar(self, orient=VERTICAL)
1956 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1957 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001958 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001959 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1960 vscrollbar.config(command=canvas.yview)
1961
csabella7eb58832017-07-04 21:30:58 -04001962 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001963 canvas.xview_moveto(0)
1964 canvas.yview_moveto(0)
1965
csabella7eb58832017-07-04 21:30:58 -04001966 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001967 self.interior = interior = Frame(canvas)
1968 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1969
csabella7eb58832017-07-04 21:30:58 -04001970 # Track changes to the canvas and frame width and sync them,
1971 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001972 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04001973 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001974 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1975 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001976 interior.bind('<Configure>', _configure_interior)
1977
1978 def _configure_canvas(event):
1979 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04001980 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001981 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1982 canvas.bind('<Configure>', _configure_canvas)
1983
1984 return
1985
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001986
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00001987if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04001988 import unittest
1989 unittest.main('idlelib.idle_test.test_configdialog',
1990 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04001991 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04001992 run(ConfigDialog)