blob: 87e0d685b778c5525c4f10b92985de723d6f7912 [file] [log] [blame]
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +00001"""IDLE Configuration Dialog: support user customization of IDLE by GUI
2
3Customize font faces, sizes, and colorization attributes. Set indentation
4defaults. Customize keybindings. Colorization and keybindings can be
5saved as user defined sets. Select startup options including shell/editor
6and default window size. Define additional help sources.
7
8Note that tab width in IDLE is currently fixed at eight due to Tk issues.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00009Refer to comments in EditorWindow autoindent code for details.
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +000010
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000011"""
csabellabac7d332017-06-26 17:46:26 -040012from tkinter import (Toplevel, Frame, LabelFrame, Listbox, Label, Button,
13 Entry, Text, Scale, Radiobutton, Checkbutton, Canvas,
14 StringVar, BooleanVar, IntVar, TRUE, FALSE,
15 TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE, NORMAL, DISABLED,
16 NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
Louie Lubb2bae82017-07-10 06:57:18 +080017 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
Terry Jan Reedy01e35752016-06-10 18:19:21 -040018from tkinter.ttk import Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000019import tkinter.colorchooser as tkColorChooser
20import tkinter.font as tkFont
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021import tkinter.messagebox as tkMessageBox
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000022
terryjreedy349abd92017-07-07 16:00:57 -040023from idlelib.config import idleConf, ConfigChanges
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040024from idlelib.config_key import GetKeysDialog
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040025from idlelib.dynoption import DynOptionMenu
26from idlelib import macosx
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -040027from idlelib.query import SectionName, HelpSource
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040028from idlelib.tabbedpages import TabbedPageSet
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040029from idlelib.textview import view_text
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -040030
terryjreedy349abd92017-07-07 16:00:57 -040031changes = ConfigChanges()
32
csabella5b591542017-07-28 14:40:59 -040033
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000034class ConfigDialog(Toplevel):
csabella7eb58832017-07-04 21:30:58 -040035 """Config dialog for IDLE.
36 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000037
Terry Jan Reedycd567362014-10-17 01:31:35 -040038 def __init__(self, parent, title='', _htest=False, _utest=False):
csabella7eb58832017-07-04 21:30:58 -040039 """Show the tabbed dialog for user configuration.
40
csabella36329a42017-07-13 23:32:01 -040041 Args:
42 parent - parent of this dialog
43 title - string which is the title of this popup dialog
44 _htest - bool, change box location when running htest
45 _utest - bool, don't wait_window when running unittest
46
47 Note: Focus set on font page fontlist.
48
49 Methods:
50 create_widgets
51 cancel: Bound to DELETE_WINDOW protocol.
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040052 """
Steven M. Gavad721c482001-07-31 10:46:53 +000053 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040054 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040055 if _htest:
56 parent.instance_dict = {}
Louie Lu9b622fb2017-07-14 08:35:48 +080057 if not _utest:
58 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000059
Steven M. Gavad721c482001-07-31 10:46:53 +000060 self.configure(borderwidth=5)
Terry Jan Reedycd567362014-10-17 01:31:35 -040061 self.title(title or 'IDLE Preferences')
csabellabac7d332017-06-26 17:46:26 -040062 x = parent.winfo_rootx() + 20
63 y = parent.winfo_rooty() + (30 if not _htest else 150)
64 self.geometry(f'+{x}+{y}')
csabella7eb58832017-07-04 21:30:58 -040065 # Each theme element key is its display name.
66 # The first value of the tuple is the sample area tag name.
67 # The second value is the display name list sort index.
csabellabac7d332017-06-26 17:46:26 -040068 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040069 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000070 self.transient(parent)
csabellabac7d332017-06-26 17:46:26 -040071 self.protocol("WM_DELETE_WINDOW", self.cancel)
Louie Lubb2bae82017-07-10 06:57:18 +080072 self.fontlist.focus_set()
csabella7eb58832017-07-04 21:30:58 -040073 # XXX Decide whether to keep or delete these key bindings.
74 # Key bindings for this dialog.
75 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
76 # self.bind('<Alt-a>', self.Apply) #apply changes, save
77 # self.bind('<F1>', self.Help) #context help
csabellabac7d332017-06-26 17:46:26 -040078 self.load_configs()
csabella5b591542017-07-28 14:40:59 -040079 # Avoid callbacks during load_configs.
80 tracers.attach()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000081
Terry Jan Reedycfa89502014-07-14 23:07:32 -040082 if not _utest:
Louie Lu9b622fb2017-07-14 08:35:48 +080083 self.grab_set()
Terry Jan Reedycfa89502014-07-14 23:07:32 -040084 self.wm_deiconify()
85 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000086
csabella5b591542017-07-28 14:40:59 -040087
csabellabac7d332017-06-26 17:46:26 -040088 def create_widgets(self):
csabella36329a42017-07-13 23:32:01 -040089 """Create and place widgets for tabbed dialog.
90
91 Widgets Bound to self:
92 tab_pages: TabbedPageSet
93
94 Methods:
95 create_page_font_tab
96 create_page_highlight
97 create_page_keys
98 create_page_general
99 create_page_extensions
100 create_action_buttons
101 load_configs: Load pages except for extensions.
csabella36329a42017-07-13 23:32:01 -0400102 activate_config_changes: Tell editors to reload.
103 """
csabellabac7d332017-06-26 17:46:26 -0400104 self.tab_pages = TabbedPageSet(self,
Terry Jan Reedy93f35422015-10-13 22:03:51 -0400105 page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
106 'Extensions'])
csabellabac7d332017-06-26 17:46:26 -0400107 self.tab_pages.pack(side=TOP, expand=TRUE, fill=BOTH)
108 self.create_page_font_tab()
109 self.create_page_highlight()
110 self.create_page_keys()
111 self.create_page_general()
112 self.create_page_extensions()
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400113 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400114
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400115 def load_configs(self):
116 """Load configuration for each page.
117
118 Load configuration from default and user config files and populate
119 the widgets on the config dialog pages.
120
121 Methods:
122 load_font_cfg
123 load_tab_cfg
124 load_theme_cfg
125 load_key_cfg
126 load_general_cfg
127 """
128 self.load_font_cfg()
129 self.load_tab_cfg()
130 self.load_theme_cfg()
131 self.load_key_cfg()
132 self.load_general_cfg()
133 # note: extension page handled separately
134
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400135 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400136 """Return frame of action buttons for dialog.
137
138 Methods:
139 ok
140 apply
141 cancel
142 help
143
144 Widget Structure:
145 outer: Frame
146 buttons: Frame
147 (no assignment): Button (ok)
148 (no assignment): Button (apply)
149 (no assignment): Button (cancel)
150 (no assignment): Button (help)
151 (no assignment): Frame
152 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400153 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400154 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400155 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400156 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000157 else:
csabellabac7d332017-06-26 17:46:26 -0400158 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400159 outer = Frame(self, pady=2)
160 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400161 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400162 ('Ok', self.ok),
163 ('Apply', self.apply),
164 ('Cancel', self.cancel),
165 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400166 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
csabellabac7d332017-06-26 17:46:26 -0400167 **padding_args).pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400168 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400169 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
170 buttons.pack(side=BOTTOM)
171 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400172
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400173 def ok(self):
174 """Apply config changes, then dismiss dialog.
175
176 Methods:
177 apply
178 destroy: inherited
179 """
180 self.apply()
181 self.destroy()
182
183 def apply(self):
184 """Apply config changes and leave dialog open.
185
186 Methods:
187 deactivate_current_config
188 save_all_changed_extensions
189 activate_config_changes
190 """
191 self.deactivate_current_config()
192 changes.save_all()
193 self.save_all_changed_extensions()
194 self.activate_config_changes()
195
196 def cancel(self):
197 """Dismiss config dialog.
198
199 Methods:
200 destroy: inherited
201 """
202 self.destroy()
203
204 def help(self):
205 """Create textview for config dialog help.
206
207 Attrbutes accessed:
208 tab_pages
209
210 Methods:
211 view_text: Method from textview module.
212 """
213 page = self.tab_pages._current_page
214 view_text(self, title='Help for IDLE preferences',
215 text=help_common+help_pages.get(page, ''))
216
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400217
csabellabac7d332017-06-26 17:46:26 -0400218 def create_page_font_tab(self):
csabella7eb58832017-07-04 21:30:58 -0400219 """Return frame of widgets for Font/Tabs tab.
220
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400221 Fonts: Enable users to provisionally change font face, size, or
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400222 boldness and to see the consequence of proposed choices. Each
223 action set 3 options in changes structuree and changes the
224 corresponding aspect of the font sample on this page and
225 highlight sample on highlight page.
226
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400227 Funtion load_font_cfg initializes font vars and widgets from
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400228 idleConf entries and tk.
229
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400230 Fontlist: mouse button 1 click or up or down key invoke
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400231 on_fontlist_select(), which sets var font_name.
Terry Jan Reedy616ecf12017-07-22 00:36:13 -0400232
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400233 Sizelist: clicking the menubutton opens the dropdown menu. A
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400234 mouse button 1 click or return key sets var font_size.
csabella36329a42017-07-13 23:32:01 -0400235
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400236 Bold_toggle: clicking the box toggles var font_bold.
csabella36329a42017-07-13 23:32:01 -0400237
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400238 Changing any of the font vars invokes var_changed_font, which
239 adds all 3 font options to changes and calls set_samples.
240 Set_samples applies a new font constructed from the font vars to
241 font_sample and to highlight_sample on the hightlight page.
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400242
243 Tabs: Enable users to change spaces entered for indent tabs.
244 Changing indent_scale value with the mouse sets Var space_num,
csabella5b591542017-07-28 14:40:59 -0400245 which invokes the default callback to add an entry to
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400246 changes. Load_tab_cfg initializes space_num to default.
csabella36329a42017-07-13 23:32:01 -0400247
248 Widget Structure: (*) widgets bound to self
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400249 frame (of tab_pages)
csabella36329a42017-07-13 23:32:01 -0400250 frame_font: LabelFrame
251 frame_font_name: Frame
252 font_name_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400253 (*)fontlist: ListBox - font_name
csabella36329a42017-07-13 23:32:01 -0400254 scroll_font: Scrollbar
255 frame_font_param: Frame
256 font_size_title: Label
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400257 (*)sizelist: DynOptionMenu - font_size
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400258 (*)bold_toggle: Checkbutton - font_bold
csabella36329a42017-07-13 23:32:01 -0400259 frame_font_sample: Frame
260 (*)font_sample: Label
261 frame_indent: LabelFrame
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400262 indent_title: Label
263 (*)indent_scale: Scale - space_num
csabella7eb58832017-07-04 21:30:58 -0400264 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400265 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400266 self.font_name = tracers.add(StringVar(parent), self.var_changed_font)
267 self.font_size = tracers.add(StringVar(parent), self.var_changed_font)
268 self.font_bold = tracers.add(BooleanVar(parent), self.var_changed_font)
269 self.space_num = tracers.add(IntVar(parent), ('main', 'Indent', 'num-spaces'))
Terry Jan Reedy22405332014-07-30 19:24:32 -0400270
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400271 # Create widgets:
Louie Lubb2bae82017-07-10 06:57:18 +0800272 # body and body section frames.
csabellabac7d332017-06-26 17:46:26 -0400273 frame = self.tab_pages.pages['Fonts/Tabs'].frame
csabellabac7d332017-06-26 17:46:26 -0400274 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400275 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
csabellabac7d332017-06-26 17:46:26 -0400276 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400277 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400278 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400279 frame_font_name = Frame(frame_font)
280 frame_font_param = Frame(frame_font)
281 font_name_title = Label(
282 frame_font_name, justify=LEFT, text='Font Face :')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400283 self.fontlist = Listbox(frame_font_name, height=5,
284 takefocus=FALSE, exportselection=FALSE)
terryjreedy5b62b352017-07-11 01:58:04 -0400285 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
286 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
287 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
csabellabac7d332017-06-26 17:46:26 -0400288 scroll_font = Scrollbar(frame_font_name)
Louie Lubb2bae82017-07-10 06:57:18 +0800289 scroll_font.config(command=self.fontlist.yview)
290 self.fontlist.config(yscrollcommand=scroll_font.set)
csabellabac7d332017-06-26 17:46:26 -0400291 font_size_title = Label(frame_font_param, text='Size :')
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400292 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400293 self.bold_toggle = Checkbutton(
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400294 frame_font_param, variable=self.font_bold,
295 onvalue=1, offvalue=0, text='Bold')
csabellabac7d332017-06-26 17:46:26 -0400296 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400297 temp_font = tkFont.Font(parent, ('courier', 10, 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400298 self.font_sample = Label(
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400299 frame_font_sample, justify=LEFT, font=temp_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400300 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400301 # frame_indent.
302 indent_title = Label(
303 frame_indent, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400304 text='Python Standard: 4 Spaces!')
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400305 self.indent_scale = Scale(
306 frame_indent, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400307 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400308
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400309 # Pack widgets:
310 # body.
csabellabac7d332017-06-26 17:46:26 -0400311 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
312 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400313 # frame_font.
csabellabac7d332017-06-26 17:46:26 -0400314 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
315 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
316 font_name_title.pack(side=TOP, anchor=W)
Louie Lubb2bae82017-07-10 06:57:18 +0800317 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400318 scroll_font.pack(side=LEFT, fill=Y)
319 font_size_title.pack(side=LEFT, anchor=W)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400320 self.sizelist.pack(side=LEFT, anchor=W)
Terry Jan Reedy7c5798e2017-07-21 03:47:01 -0400321 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
csabellabac7d332017-06-26 17:46:26 -0400322 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
323 self.font_sample.pack(expand=TRUE, fill=BOTH)
Terry Jan Reedy07ba3052017-07-23 12:20:08 -0400324 # frame_indent.
325 frame_indent.pack(side=TOP, fill=X)
326 indent_title.pack(side=TOP, anchor=W, padx=5)
327 self.indent_scale.pack(side=TOP, padx=5, fill=X)
Louie Lubb2bae82017-07-10 06:57:18 +0800328
Steven M. Gava952d0a52001-08-03 04:43:44 +0000329 return frame
330
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400331 def load_font_cfg(self):
332 """Load current configuration settings for the font options.
333
334 Retrieve current font with idleConf.GetFont and font families
335 from tk. Setup fontlist and set font_name. Setup sizelist,
336 which sets font_size. Set font_bold. Setting font variables
337 calls set_samples (thrice).
338 """
339 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
340 font_name = configured_font[0].lower()
341 font_size = configured_font[1]
342 font_bold = configured_font[2]=='bold'
343
344 # Set editor font selection list and font_name.
345 fonts = list(tkFont.families(self))
346 fonts.sort()
347 for font in fonts:
348 self.fontlist.insert(END, font)
349 self.font_name.set(font_name)
350 lc_fonts = [s.lower() for s in fonts]
351 try:
352 current_font_index = lc_fonts.index(font_name)
353 self.fontlist.see(current_font_index)
354 self.fontlist.select_set(current_font_index)
355 self.fontlist.select_anchor(current_font_index)
356 self.fontlist.activate(current_font_index)
357 except ValueError:
358 pass
359 # Set font size dropdown.
360 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
361 '16', '18', '20', '22', '25', '29', '34', '40'),
362 font_size)
363 # Set font weight.
364 self.font_bold.set(font_bold)
365
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400366 def var_changed_font(self, *params):
367 """Store changes to font attributes.
368
369 When one font attribute changes, save them all, as they are
370 not independent from each other. In particular, when we are
371 overriding the default font, we need to write out everything.
372 """
373 value = self.font_name.get()
374 changes.add_option('main', 'EditorWindow', 'font', value)
375 value = self.font_size.get()
376 changes.add_option('main', 'EditorWindow', 'font-size', value)
377 value = self.font_bold.get()
378 changes.add_option('main', 'EditorWindow', 'font-bold', value)
379 self.set_samples()
380
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400381 def on_fontlist_select(self, event):
382 """Handle selecting a font from the list.
383
384 Event can result from either mouse click or Up or Down key.
385 Set font_name and example displays to selection.
386 """
387 font = self.fontlist.get(
388 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
389 self.font_name.set(font.lower())
390
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400391 def set_samples(self, event=None):
392 """Update update both screen samples with the font settings.
393
394 Called on font initialization and change events.
395 Accesses font_name, font_size, and font_bold Variables.
396 Updates font_sample and hightlight page highlight_sample.
397 """
398 font_name = self.font_name.get()
399 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
400 new_font = (font_name, self.font_size.get(), font_weight)
401 self.font_sample['font'] = new_font
402 self.highlight_sample['font'] = new_font
403
404 def load_tab_cfg(self):
405 """Load current configuration settings for the tab options.
406
407 Attributes updated:
408 space_num: Set to value from idleConf.
409 """
410 # Set indent sizes.
411 space_num = idleConf.GetOption(
412 'main', 'Indent', 'num-spaces', default=4, type='int')
413 self.space_num.set(space_num)
414
csabellabac7d332017-06-26 17:46:26 -0400415 def create_page_highlight(self):
csabella7eb58832017-07-04 21:30:58 -0400416 """Return frame of widgets for Highlighting tab.
417
csabella36329a42017-07-13 23:32:01 -0400418 Tk Variables:
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400419 color: Color of selected target.
csabella7eb58832017-07-04 21:30:58 -0400420 builtin_theme: Menu variable for built-in theme.
421 custom_theme: Menu variable for custom theme.
422 fg_bg_toggle: Toggle for foreground/background color.
csabella36329a42017-07-13 23:32:01 -0400423 Note: this has no callback.
csabella7eb58832017-07-04 21:30:58 -0400424 is_builtin_theme: Selector for built-in or custom theme.
425 highlight_target: Menu variable for the highlight tag target.
csabella36329a42017-07-13 23:32:01 -0400426
427 Instance Data Attributes:
428 theme_elements: Dictionary of tags for text highlighting.
429 The key is the display name and the value is a tuple of
430 (tag name, display sort order).
431
432 Methods [attachment]:
433 load_theme_cfg: Load current highlight colors.
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400434 get_color: Invoke colorchooser [button_set_color].
435 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
csabella36329a42017-07-13 23:32:01 -0400436 set_highlight_target: set fg_bg_toggle, set_color_sample().
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400437 set_color_sample: Set frame background to target.
438 on_new_color_set: Set new color and add option.
csabella36329a42017-07-13 23:32:01 -0400439 paint_theme_sample: Recolor sample.
440 get_new_theme_name: Get from popup.
441 create_new_theme: Combine theme with changes and save.
442 save_as_new_theme: Save [button_save_custom_theme].
443 set_theme_type: Command for [is_builtin_theme].
444 delete_custom_theme: Ativate default [button_delete_custom_theme].
445 save_new_theme: Save to userCfg['theme'] (is function).
446
447 Widget Structure: (*) widgets bound to self
448 frame
449 frame_custom: LabelFrame
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400450 (*)highlight_sample: Text
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400451 (*)frame_color_set: Frame
452 button_set_color: Button
csabella36329a42017-07-13 23:32:01 -0400453 (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
454 frame_fg_bg_toggle: Frame
455 (*)radio_fg: Radiobutton - fg_bg_toggle
456 (*)radio_bg: Radiobutton - fg_bg_toggle
457 button_save_custom_theme: Button
458 frame_theme: LabelFrame
459 theme_type_title: Label
460 (*)radio_theme_builtin: Radiobutton - is_builtin_theme
461 (*)radio_theme_custom: Radiobutton - is_builtin_theme
462 (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
463 (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
464 (*)button_delete_custom_theme: Button
465 (*)new_custom_theme: Label
csabella7eb58832017-07-04 21:30:58 -0400466 """
csabella36329a42017-07-13 23:32:01 -0400467 self.theme_elements={
468 'Normal Text': ('normal', '00'),
469 'Python Keywords': ('keyword', '01'),
470 'Python Definitions': ('definition', '02'),
471 'Python Builtins': ('builtin', '03'),
472 'Python Comments': ('comment', '04'),
473 'Python Strings': ('string', '05'),
474 'Selected Text': ('hilite', '06'),
475 'Found Text': ('hit', '07'),
476 'Cursor': ('cursor', '08'),
477 'Editor Breakpoint': ('break', '09'),
478 'Shell Normal Text': ('console', '10'),
479 'Shell Error Text': ('error', '11'),
480 'Shell Stdout Text': ('stdout', '12'),
481 'Shell Stderr Text': ('stderr', '13'),
482 }
Terry Jan Reedy22405332014-07-30 19:24:32 -0400483 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400484 self.builtin_theme = tracers.add(
485 StringVar(parent), self.var_changed_builtin_theme)
486 self.custom_theme = tracers.add(
487 StringVar(parent), self.var_changed_custom_theme)
csabellabac7d332017-06-26 17:46:26 -0400488 self.fg_bg_toggle = BooleanVar(parent)
csabella5b591542017-07-28 14:40:59 -0400489 self.color = tracers.add(
490 StringVar(parent), self.var_changed_color)
491 self.is_builtin_theme = tracers.add(
492 BooleanVar(parent), self.var_changed_is_builtin_theme)
493 self.highlight_target = tracers.add(
494 StringVar(parent), self.var_changed_highlight_target)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400495
Steven M. Gava952d0a52001-08-03 04:43:44 +0000496 ##widget creation
497 #body frame
csabellabac7d332017-06-26 17:46:26 -0400498 frame = self.tab_pages.pages['Highlighting'].frame
Steven M. Gava952d0a52001-08-03 04:43:44 +0000499 #body section frames
csabellabac7d332017-06-26 17:46:26 -0400500 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400501 text=' Custom Highlighting ')
csabellabac7d332017-06-26 17:46:26 -0400502 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400503 text=' Highlighting Theme ')
csabellabac7d332017-06-26 17:46:26 -0400504 #frame_custom
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400505 self.highlight_sample=Text(
csabellabac7d332017-06-26 17:46:26 -0400506 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400507 font=('courier', 12, ''), cursor='hand2', width=21, height=11,
508 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400509 text=self.highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400510 text.bind('<Double-Button-1>', lambda e: 'break')
511 text.bind('<B1-Motion>', lambda e: 'break')
csabellabac7d332017-06-26 17:46:26 -0400512 text_and_tags=(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400513 ('#you can click here', 'comment'), ('\n', 'normal'),
514 ('#to choose items', 'comment'), ('\n', 'normal'),
515 ('def', 'keyword'), (' ', 'normal'),
516 ('func', 'definition'), ('(param):\n ', 'normal'),
517 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
518 ("'string'", 'string'), ('\n var1 = ', 'normal'),
519 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
520 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
521 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400522 ('None', 'keyword'), (')\n', 'normal'),
523 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400524 (' error ', 'error'), (' ', 'normal'),
525 ('cursor |', 'cursor'), ('\n ', 'normal'),
526 ('shell', 'console'), (' ', 'normal'),
527 ('stdout', 'stdout'), (' ', 'normal'),
528 ('stderr', 'stderr'), ('\n', 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400529 for texttag in text_and_tags:
530 text.insert(END, texttag[0], texttag[1])
531 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400532 def tem(event, elem=element):
csabellabac7d332017-06-26 17:46:26 -0400533 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400534 text.tag_bind(
csabellabac7d332017-06-26 17:46:26 -0400535 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400536 text['state'] = DISABLED
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400537 self.frame_color_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
csabellabac7d332017-06-26 17:46:26 -0400538 frame_fg_bg_toggle = Frame(frame_custom)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400539 button_set_color = Button(
540 self.frame_color_set, text='Choose Color for :',
541 command=self.get_color, highlightthickness=0)
csabellabac7d332017-06-26 17:46:26 -0400542 self.opt_menu_highlight_target = DynOptionMenu(
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400543 self.frame_color_set, self.highlight_target, None,
csabellabac7d332017-06-26 17:46:26 -0400544 highlightthickness=0) #, command=self.set_highlight_targetBinding
545 self.radio_fg = Radiobutton(
546 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400547 text='Foreground', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400548 self.radio_bg=Radiobutton(
549 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400550 text='Background', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400551 self.fg_bg_toggle.set(1)
552 button_save_custom_theme = Button(
553 frame_custom, text='Save as New Custom Theme',
554 command=self.save_as_new_theme)
555 #frame_theme
556 theme_type_title = Label(frame_theme, text='Select : ')
557 self.radio_theme_builtin = Radiobutton(
558 frame_theme, variable=self.is_builtin_theme, value=1,
559 command=self.set_theme_type, text='a Built-in Theme')
560 self.radio_theme_custom = Radiobutton(
561 frame_theme, variable=self.is_builtin_theme, value=0,
562 command=self.set_theme_type, text='a Custom Theme')
563 self.opt_menu_theme_builtin = DynOptionMenu(
564 frame_theme, self.builtin_theme, None, command=None)
565 self.opt_menu_theme_custom=DynOptionMenu(
566 frame_theme, self.custom_theme, None, command=None)
567 self.button_delete_custom_theme=Button(
568 frame_theme, text='Delete Custom Theme',
569 command=self.delete_custom_theme)
570 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400571
Steven M. Gava952d0a52001-08-03 04:43:44 +0000572 ##widget packing
573 #body
csabellabac7d332017-06-26 17:46:26 -0400574 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
575 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
576 #frame_custom
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400577 self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400578 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400579 self.highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400580 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400581 button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
csabellabac7d332017-06-26 17:46:26 -0400582 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400583 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
csabellabac7d332017-06-26 17:46:26 -0400584 self.radio_fg.pack(side=LEFT, anchor=E)
585 self.radio_bg.pack(side=RIGHT, anchor=W)
586 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
587 #frame_theme
588 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
589 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
590 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
591 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
592 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
593 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500594 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000595 return frame
596
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400597 def load_theme_cfg(self):
598 """Load current configuration settings for the theme options.
599
600 Based on the is_builtin_theme toggle, the theme is set as
601 either builtin or custom and the initial widget values
602 reflect the current settings from idleConf.
603
604 Attributes updated:
605 is_builtin_theme: Set from idleConf.
606 opt_menu_theme_builtin: List of default themes from idleConf.
607 opt_menu_theme_custom: List of custom themes from idleConf.
608 radio_theme_custom: Disabled if there are no custom themes.
609 custom_theme: Message with additional information.
610 opt_menu_highlight_target: Create menu from self.theme_elements.
611
612 Methods:
613 set_theme_type
614 paint_theme_sample
615 set_highlight_target
616 """
617 # Set current theme type radiobutton.
618 self.is_builtin_theme.set(idleConf.GetOption(
619 'main', 'Theme', 'default', type='bool', default=1))
620 # Set current theme.
621 current_option = idleConf.CurrentTheme()
622 # Load available theme option menus.
623 if self.is_builtin_theme.get(): # Default theme selected.
624 item_list = idleConf.GetSectionList('default', 'highlight')
625 item_list.sort()
626 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
627 item_list = idleConf.GetSectionList('user', 'highlight')
628 item_list.sort()
629 if not item_list:
630 self.radio_theme_custom['state'] = DISABLED
631 self.custom_theme.set('- no custom themes -')
632 else:
633 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
634 else: # User theme selected.
635 item_list = idleConf.GetSectionList('user', 'highlight')
636 item_list.sort()
637 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
638 item_list = idleConf.GetSectionList('default', 'highlight')
639 item_list.sort()
640 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
641 self.set_theme_type()
642 # Load theme element option menu.
643 theme_names = list(self.theme_elements.keys())
644 theme_names.sort(key=lambda x: self.theme_elements[x][1])
645 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
646 self.paint_theme_sample()
647 self.set_highlight_target()
648
649 def var_changed_builtin_theme(self, *params):
650 """Process new builtin theme selection.
651
652 Add the changed theme's name to the changed_items and recreate
653 the sample with the values from the selected theme.
654 """
655 old_themes = ('IDLE Classic', 'IDLE New')
656 value = self.builtin_theme.get()
657 if value not in old_themes:
658 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
659 changes.add_option('main', 'Theme', 'name', old_themes[0])
660 changes.add_option('main', 'Theme', 'name2', value)
661 self.new_custom_theme.config(text='New theme, see Help',
662 fg='#500000')
663 else:
664 changes.add_option('main', 'Theme', 'name', value)
665 changes.add_option('main', 'Theme', 'name2', '')
666 self.new_custom_theme.config(text='', fg='black')
667 self.paint_theme_sample()
668
669 def var_changed_custom_theme(self, *params):
670 """Process new custom theme selection.
671
672 If a new custom theme is selected, add the name to the
673 changed_items and apply the theme to the sample.
674 """
675 value = self.custom_theme.get()
676 if value != '- no custom themes -':
677 changes.add_option('main', 'Theme', 'name', value)
678 self.paint_theme_sample()
679
680 def var_changed_is_builtin_theme(self, *params):
681 """Process toggle between builtin and custom theme.
682
683 Update the default toggle value and apply the newly
684 selected theme type.
685 """
686 value = self.is_builtin_theme.get()
687 changes.add_option('main', 'Theme', 'default', value)
688 if value:
689 self.var_changed_builtin_theme()
690 else:
691 self.var_changed_custom_theme()
692
693 def var_changed_color(self, *params):
694 "Process change to color choice."
695 self.on_new_color_set()
696
697 def var_changed_highlight_target(self, *params):
698 "Process selection of new target tag for highlighting."
699 self.set_highlight_target()
700
701 def set_theme_type(self):
702 """Set available screen options based on builtin or custom theme.
703
704 Attributes accessed:
705 is_builtin_theme
706
707 Attributes updated:
708 opt_menu_theme_builtin
709 opt_menu_theme_custom
710 button_delete_custom_theme
711 radio_theme_custom
712
713 Called from:
714 handler for radio_theme_builtin and radio_theme_custom
715 delete_custom_theme
716 create_new_theme
717 load_theme_cfg
718 """
719 if self.is_builtin_theme.get():
720 self.opt_menu_theme_builtin['state'] = NORMAL
721 self.opt_menu_theme_custom['state'] = DISABLED
722 self.button_delete_custom_theme['state'] = DISABLED
723 else:
724 self.opt_menu_theme_builtin['state'] = DISABLED
725 self.radio_theme_custom['state'] = NORMAL
726 self.opt_menu_theme_custom['state'] = NORMAL
727 self.button_delete_custom_theme['state'] = NORMAL
728
729 def get_color(self):
730 """Handle button to select a new color for the target tag.
731
732 If a new color is selected while using a builtin theme, a
733 name must be supplied to create a custom theme.
734
735 Attributes accessed:
736 highlight_target
737 frame_color_set
738 is_builtin_theme
739
740 Attributes updated:
741 color
742
743 Methods:
744 get_new_theme_name
745 create_new_theme
746 """
747 target = self.highlight_target.get()
748 prev_color = self.frame_color_set.cget('bg')
749 rgbTuplet, color_string = tkColorChooser.askcolor(
750 parent=self, title='Pick new color for : '+target,
751 initialcolor=prev_color)
752 if color_string and (color_string != prev_color):
753 # User didn't cancel and they chose a new color.
754 if self.is_builtin_theme.get(): # Current theme is a built-in.
755 message = ('Your changes will be saved as a new Custom Theme. '
756 'Enter a name for your new Custom Theme below.')
757 new_theme = self.get_new_theme_name(message)
758 if not new_theme: # User cancelled custom theme creation.
759 return
760 else: # Create new custom theme based on previously active theme.
761 self.create_new_theme(new_theme)
762 self.color.set(color_string)
763 else: # Current theme is user defined.
764 self.color.set(color_string)
765
766 def on_new_color_set(self):
767 "Display sample of new color selection on the dialog."
768 new_color=self.color.get()
769 self.frame_color_set.config(bg=new_color) # Set sample.
770 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
771 sample_element = self.theme_elements[self.highlight_target.get()][0]
772 self.highlight_sample.tag_config(sample_element, **{plane:new_color})
773 theme = self.custom_theme.get()
774 theme_element = sample_element + '-' + plane
775 changes.add_option('highlight', theme, theme_element, new_color)
776
777 def get_new_theme_name(self, message):
778 "Return name of new theme from query popup."
779 used_names = (idleConf.GetSectionList('user', 'highlight') +
780 idleConf.GetSectionList('default', 'highlight'))
781 new_theme = SectionName(
782 self, 'New Custom Theme', message, used_names).result
783 return new_theme
784
785 def save_as_new_theme(self):
786 """Prompt for new theme name and create the theme.
787
788 Methods:
789 get_new_theme_name
790 create_new_theme
791 """
792 new_theme_name = self.get_new_theme_name('New Theme Name:')
793 if new_theme_name:
794 self.create_new_theme(new_theme_name)
795
796 def create_new_theme(self, new_theme_name):
797 """Create a new custom theme with the given name.
798
799 Create the new theme based on the previously active theme
800 with the current changes applied. Once it is saved, then
801 activate the new theme.
802
803 Attributes accessed:
804 builtin_theme
805 custom_theme
806
807 Attributes updated:
808 opt_menu_theme_custom
809 is_builtin_theme
810
811 Method:
812 save_new_theme
813 set_theme_type
814 """
815 if self.is_builtin_theme.get():
816 theme_type = 'default'
817 theme_name = self.builtin_theme.get()
818 else:
819 theme_type = 'user'
820 theme_name = self.custom_theme.get()
821 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
822 # Apply any of the old theme's unsaved changes to the new theme.
823 if theme_name in changes['highlight']:
824 theme_changes = changes['highlight'][theme_name]
825 for element in theme_changes:
826 new_theme[element] = theme_changes[element]
827 # Save the new theme.
828 self.save_new_theme(new_theme_name, new_theme)
829 # Change GUI over to the new theme.
830 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
831 custom_theme_list.sort()
832 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
833 self.is_builtin_theme.set(0)
834 self.set_theme_type()
835
836 def set_highlight_target(self):
837 """Set fg/bg toggle and color based on highlight tag target.
838
839 Instance variables accessed:
840 highlight_target
841
842 Attributes updated:
843 radio_fg
844 radio_bg
845 fg_bg_toggle
846
847 Methods:
848 set_color_sample
849
850 Called from:
851 var_changed_highlight_target
852 load_theme_cfg
853 """
854 if self.highlight_target.get() == 'Cursor': # bg not possible
855 self.radio_fg['state'] = DISABLED
856 self.radio_bg['state'] = DISABLED
857 self.fg_bg_toggle.set(1)
858 else: # Both fg and bg can be set.
859 self.radio_fg['state'] = NORMAL
860 self.radio_bg['state'] = NORMAL
861 self.fg_bg_toggle.set(1)
862 self.set_color_sample()
863
864 def set_color_sample_binding(self, *args):
865 """Change color sample based on foreground/background toggle.
866
867 Methods:
868 set_color_sample
869 """
870 self.set_color_sample()
871
872 def set_color_sample(self):
873 """Set the color of the frame background to reflect the selected target.
874
875 Instance variables accessed:
876 theme_elements
877 highlight_target
878 fg_bg_toggle
879 highlight_sample
880
881 Attributes updated:
882 frame_color_set
883 """
884 # Set the color sample area.
885 tag = self.theme_elements[self.highlight_target.get()][0]
886 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
887 color = self.highlight_sample.tag_cget(tag, plane)
888 self.frame_color_set.config(bg=color)
889
890 def paint_theme_sample(self):
891 """Apply the theme colors to each element tag in the sample text.
892
893 Instance attributes accessed:
894 theme_elements
895 is_builtin_theme
896 builtin_theme
897 custom_theme
898
899 Attributes updated:
900 highlight_sample: Set the tag elements to the theme.
901
902 Methods:
903 set_color_sample
904
905 Called from:
906 var_changed_builtin_theme
907 var_changed_custom_theme
908 load_theme_cfg
909 """
910 if self.is_builtin_theme.get(): # Default theme
911 theme = self.builtin_theme.get()
912 else: # User theme
913 theme = self.custom_theme.get()
914 for element_title in self.theme_elements:
915 element = self.theme_elements[element_title][0]
916 colors = idleConf.GetHighlight(theme, element)
917 if element == 'cursor': # Cursor sample needs special painting.
918 colors['background'] = idleConf.GetHighlight(
919 theme, 'normal', fgBg='bg')
920 # Handle any unsaved changes to this theme.
921 if theme in changes['highlight']:
922 theme_dict = changes['highlight'][theme]
923 if element + '-foreground' in theme_dict:
924 colors['foreground'] = theme_dict[element + '-foreground']
925 if element + '-background' in theme_dict:
926 colors['background'] = theme_dict[element + '-background']
927 self.highlight_sample.tag_config(element, **colors)
928 self.set_color_sample()
929
930 def save_new_theme(self, theme_name, theme):
931 """Save a newly created theme to idleConf.
932
933 theme_name - string, the name of the new theme
934 theme - dictionary containing the new theme
935 """
936 if not idleConf.userCfg['highlight'].has_section(theme_name):
937 idleConf.userCfg['highlight'].add_section(theme_name)
938 for element in theme:
939 value = theme[element]
940 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
941
942 def delete_custom_theme(self):
943 """Handle event to delete custom theme.
944
945 The current theme is deactivated and the default theme is
946 activated. The custom theme is permanently removed from
947 the config file.
948
949 Attributes accessed:
950 custom_theme
951
952 Attributes updated:
953 radio_theme_custom
954 opt_menu_theme_custom
955 is_builtin_theme
956 builtin_theme
957
958 Methods:
959 deactivate_current_config
960 save_all_changed_extensions
961 activate_config_changes
962 set_theme_type
963 """
964 theme_name = self.custom_theme.get()
965 delmsg = 'Are you sure you wish to delete the theme %r ?'
966 if not tkMessageBox.askyesno(
967 'Delete Theme', delmsg % theme_name, parent=self):
968 return
969 self.deactivate_current_config()
970 # Remove theme from changes, config, and file.
971 changes.delete_section('highlight', theme_name)
972 # Reload user theme list.
973 item_list = idleConf.GetSectionList('user', 'highlight')
974 item_list.sort()
975 if not item_list:
976 self.radio_theme_custom['state'] = DISABLED
977 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
978 else:
979 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
980 # Revert to default theme.
981 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
982 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
983 # User can't back out of these changes, they must be applied now.
984 changes.save_all()
985 self.save_all_changed_extensions()
986 self.activate_config_changes()
987 self.set_theme_type()
988
989
csabellabac7d332017-06-26 17:46:26 -0400990 def create_page_keys(self):
csabella7eb58832017-07-04 21:30:58 -0400991 """Return frame of widgets for Keys tab.
992
csabella36329a42017-07-13 23:32:01 -0400993 Tk Variables:
csabella7eb58832017-07-04 21:30:58 -0400994 builtin_keys: Menu variable for built-in keybindings.
995 custom_keys: Menu variable for custom keybindings.
996 are_keys_builtin: Selector for built-in or custom keybindings.
997 keybinding: Action/key bindings.
csabella36329a42017-07-13 23:32:01 -0400998
999 Methods:
1000 load_key_config: Set table.
1001 load_keys_list: Reload active set.
1002 keybinding_selected: Bound to list_bindings button release.
1003 get_new_keys: Command for button_new_keys.
1004 get_new_keys_name: Call popup.
1005 create_new_key_set: Combine active keyset and changes.
1006 set_keys_type: Command for are_keys_builtin.
1007 delete_custom_keys: Command for button_delete_custom_keys.
1008 save_as_new_key_set: Command for button_save_custom_keys.
1009 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
1010 deactivate_current_config: Remove keys bindings in editors.
1011
1012 Widget Structure: (*) widgets bound to self
1013 frame
1014 frame_custom: LabelFrame
1015 frame_target: Frame
1016 target_title: Label
1017 scroll_target_y: Scrollbar
1018 scroll_target_x: Scrollbar
1019 (*)list_bindings: ListBox
1020 (*)button_new_keys: Button
1021 frame_key_sets: LabelFrame
1022 frames[0]: Frame
1023 (*)radio_keys_builtin: Radiobutton - are_keys_builtin
1024 (*)radio_keys_custom: Radiobutton - are_keys_builtin
1025 (*)opt_menu_keys_builtin: DynOptionMenu - builtin_keys
1026 (*)opt_menu_keys_custom: DynOptionMenu - custom_keys
1027 (*)new_custom_keys: Label
1028 frames[1]: Frame
1029 (*)button_delete_custom_keys: Button
1030 button_save_custom_keys: Button
csabella7eb58832017-07-04 21:30:58 -04001031 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001032 parent = self.parent
csabella5b591542017-07-28 14:40:59 -04001033 self.builtin_keys = tracers.add(
1034 StringVar(parent), self.var_changed_builtin_keys)
1035 self.custom_keys = tracers.add(
1036 StringVar(parent), self.var_changed_custom_keys)
1037 self.are_keys_builtin = tracers.add(
1038 BooleanVar(parent), self.var_changed_are_keys_builtin)
1039 self.keybinding = tracers.add(
1040 StringVar(parent), self.var_changed_keybinding)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001041
Steven M. Gava60fc7072001-08-04 13:58:22 +00001042 ##widget creation
1043 #body frame
csabellabac7d332017-06-26 17:46:26 -04001044 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +00001045 #body section frames
csabellabac7d332017-06-26 17:46:26 -04001046 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001047 frame, borderwidth=2, relief=GROOVE,
1048 text=' Custom Key Bindings ')
csabellabac7d332017-06-26 17:46:26 -04001049 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001050 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
csabellabac7d332017-06-26 17:46:26 -04001051 #frame_custom
1052 frame_target = Frame(frame_custom)
1053 target_title = Label(frame_target, text='Action - Key(s)')
1054 scroll_target_y = Scrollbar(frame_target)
1055 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
1056 self.list_bindings = Listbox(
1057 frame_target, takefocus=FALSE, exportselection=FALSE)
1058 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
1059 scroll_target_y.config(command=self.list_bindings.yview)
1060 scroll_target_x.config(command=self.list_bindings.xview)
1061 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
1062 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
1063 self.button_new_keys = Button(
1064 frame_custom, text='Get New Keys for Selection',
1065 command=self.get_new_keys, state=DISABLED)
1066 #frame_key_sets
1067 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +00001068 for i in range(2)]
csabellabac7d332017-06-26 17:46:26 -04001069 self.radio_keys_builtin = Radiobutton(
1070 frames[0], variable=self.are_keys_builtin, value=1,
1071 command=self.set_keys_type, text='Use a Built-in Key Set')
1072 self.radio_keys_custom = Radiobutton(
1073 frames[0], variable=self.are_keys_builtin, value=0,
1074 command=self.set_keys_type, text='Use a Custom Key Set')
1075 self.opt_menu_keys_builtin = DynOptionMenu(
1076 frames[0], self.builtin_keys, None, command=None)
1077 self.opt_menu_keys_custom = DynOptionMenu(
1078 frames[0], self.custom_keys, None, command=None)
1079 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001080 frames[1], text='Delete Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001081 command=self.delete_custom_keys)
1082 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001083 frames[1], text='Save as New Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -04001084 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001085 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001086
Steven M. Gava60fc7072001-08-04 13:58:22 +00001087 ##widget packing
1088 #body
csabellabac7d332017-06-26 17:46:26 -04001089 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
1090 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
1091 #frame_custom
1092 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
1093 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +00001094 #frame target
csabellabac7d332017-06-26 17:46:26 -04001095 frame_target.columnconfigure(0, weight=1)
1096 frame_target.rowconfigure(1, weight=1)
1097 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
1098 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
1099 scroll_target_y.grid(row=1, column=1, sticky=NS)
1100 scroll_target_x.grid(row=2, column=0, sticky=EW)
1101 #frame_key_sets
1102 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
1103 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
1104 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
1105 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001106 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001107 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
1108 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +00001109 frames[0].pack(side=TOP, fill=BOTH, expand=True)
1110 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +00001111 return frame
1112
Terry Jan Reedyb1660802017-07-27 18:28:01 -04001113 def load_key_cfg(self):
1114 "Load current configuration settings for the keybinding options."
1115 # Set current keys type radiobutton.
1116 self.are_keys_builtin.set(idleConf.GetOption(
1117 'main', 'Keys', 'default', type='bool', default=1))
1118 # Set current keys.
1119 current_option = idleConf.CurrentKeys()
1120 # Load available keyset option menus.
1121 if self.are_keys_builtin.get(): # Default theme selected.
1122 item_list = idleConf.GetSectionList('default', 'keys')
1123 item_list.sort()
1124 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1125 item_list = idleConf.GetSectionList('user', 'keys')
1126 item_list.sort()
1127 if not item_list:
1128 self.radio_keys_custom['state'] = DISABLED
1129 self.custom_keys.set('- no custom keys -')
1130 else:
1131 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1132 else: # User key set selected.
1133 item_list = idleConf.GetSectionList('user', 'keys')
1134 item_list.sort()
1135 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1136 item_list = idleConf.GetSectionList('default', 'keys')
1137 item_list.sort()
1138 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1139 self.set_keys_type()
1140 # Load keyset element list.
1141 keyset_name = idleConf.CurrentKeys()
1142 self.load_keys_list(keyset_name)
1143
Terry Jan Reedyb1660802017-07-27 18:28:01 -04001144 def var_changed_builtin_keys(self, *params):
1145 "Process selection of builtin key set."
1146 old_keys = (
1147 'IDLE Classic Windows',
1148 'IDLE Classic Unix',
1149 'IDLE Classic Mac',
1150 'IDLE Classic OSX',
1151 )
1152 value = self.builtin_keys.get()
1153 if value not in old_keys:
1154 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1155 changes.add_option('main', 'Keys', 'name', old_keys[0])
1156 changes.add_option('main', 'Keys', 'name2', value)
1157 self.new_custom_keys.config(text='New key set, see Help',
1158 fg='#500000')
1159 else:
1160 changes.add_option('main', 'Keys', 'name', value)
1161 changes.add_option('main', 'Keys', 'name2', '')
1162 self.new_custom_keys.config(text='', fg='black')
1163 self.load_keys_list(value)
1164
1165 def var_changed_custom_keys(self, *params):
1166 "Process selection of custom key set."
1167 value = self.custom_keys.get()
1168 if value != '- no custom keys -':
1169 changes.add_option('main', 'Keys', 'name', value)
1170 self.load_keys_list(value)
1171
1172 def var_changed_are_keys_builtin(self, *params):
1173 "Process toggle between builtin key set and custom key set."
1174 value = self.are_keys_builtin.get()
1175 changes.add_option('main', 'Keys', 'default', value)
1176 if value:
1177 self.var_changed_builtin_keys()
1178 else:
1179 self.var_changed_custom_keys()
1180
1181 def var_changed_keybinding(self, *params):
1182 "Store change to a keybinding."
1183 value = self.keybinding.get()
1184 key_set = self.custom_keys.get()
1185 event = self.list_bindings.get(ANCHOR).split()[0]
1186 if idleConf.IsCoreBinding(event):
1187 changes.add_option('keys', key_set, event, value)
1188 else: # Event is an extension binding.
1189 ext_name = idleConf.GetExtnNameForEvent(event)
1190 ext_keybind_section = ext_name + '_cfgBindings'
1191 changes.add_option('extensions', ext_keybind_section, event, value)
1192
1193 def set_keys_type(self):
1194 "Set available screen options based on builtin or custom key set."
1195 if self.are_keys_builtin.get():
1196 self.opt_menu_keys_builtin['state'] = NORMAL
1197 self.opt_menu_keys_custom['state'] = DISABLED
1198 self.button_delete_custom_keys['state'] = DISABLED
1199 else:
1200 self.opt_menu_keys_builtin['state'] = DISABLED
1201 self.radio_keys_custom['state'] = NORMAL
1202 self.opt_menu_keys_custom['state'] = NORMAL
1203 self.button_delete_custom_keys['state'] = NORMAL
1204
1205 def get_new_keys(self):
1206 """Handle event to change key binding for selected line.
1207
1208 A selection of a key/binding in the list of current
1209 bindings pops up a dialog to enter a new binding. If
1210 the current key set is builtin and a binding has
1211 changed, then a name for a custom key set needs to be
1212 entered for the change to be applied.
1213 """
1214 list_index = self.list_bindings.index(ANCHOR)
1215 binding = self.list_bindings.get(list_index)
1216 bind_name = binding.split()[0]
1217 if self.are_keys_builtin.get():
1218 current_key_set_name = self.builtin_keys.get()
1219 else:
1220 current_key_set_name = self.custom_keys.get()
1221 current_bindings = idleConf.GetCurrentKeySet()
1222 if current_key_set_name in changes['keys']: # unsaved changes
1223 key_set_changes = changes['keys'][current_key_set_name]
1224 for event in key_set_changes:
1225 current_bindings[event] = key_set_changes[event].split()
1226 current_key_sequences = list(current_bindings.values())
1227 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1228 current_key_sequences).result
1229 if new_keys:
1230 if self.are_keys_builtin.get(): # Current key set is a built-in.
1231 message = ('Your changes will be saved as a new Custom Key Set.'
1232 ' Enter a name for your new Custom Key Set below.')
1233 new_keyset = self.get_new_keys_name(message)
1234 if not new_keyset: # User cancelled custom key set creation.
1235 self.list_bindings.select_set(list_index)
1236 self.list_bindings.select_anchor(list_index)
1237 return
1238 else: # Create new custom key set based on previously active key set.
1239 self.create_new_key_set(new_keyset)
1240 self.list_bindings.delete(list_index)
1241 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
1242 self.list_bindings.select_set(list_index)
1243 self.list_bindings.select_anchor(list_index)
1244 self.keybinding.set(new_keys)
1245 else:
1246 self.list_bindings.select_set(list_index)
1247 self.list_bindings.select_anchor(list_index)
1248
1249 def get_new_keys_name(self, message):
1250 "Return new key set name from query popup."
1251 used_names = (idleConf.GetSectionList('user', 'keys') +
1252 idleConf.GetSectionList('default', 'keys'))
1253 new_keyset = SectionName(
1254 self, 'New Custom Key Set', message, used_names).result
1255 return new_keyset
1256
1257 def save_as_new_key_set(self):
1258 "Prompt for name of new key set and save changes using that name."
1259 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1260 if new_keys_name:
1261 self.create_new_key_set(new_keys_name)
1262
1263 def keybinding_selected(self, event):
1264 "Activate button to assign new keys to selected action."
1265 self.button_new_keys['state'] = NORMAL
1266
1267 def create_new_key_set(self, new_key_set_name):
1268 """Create a new custom key set with the given name.
1269
1270 Create the new key set based on the previously active set
1271 with the current changes applied. Once it is saved, then
1272 activate the new key set.
1273 """
1274 if self.are_keys_builtin.get():
1275 prev_key_set_name = self.builtin_keys.get()
1276 else:
1277 prev_key_set_name = self.custom_keys.get()
1278 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1279 new_keys = {}
1280 for event in prev_keys: # Add key set to changed items.
1281 event_name = event[2:-2] # Trim off the angle brackets.
1282 binding = ' '.join(prev_keys[event])
1283 new_keys[event_name] = binding
1284 # Handle any unsaved changes to prev key set.
1285 if prev_key_set_name in changes['keys']:
1286 key_set_changes = changes['keys'][prev_key_set_name]
1287 for event in key_set_changes:
1288 new_keys[event] = key_set_changes[event]
1289 # Save the new key set.
1290 self.save_new_key_set(new_key_set_name, new_keys)
1291 # Change GUI over to the new key set.
1292 custom_key_list = idleConf.GetSectionList('user', 'keys')
1293 custom_key_list.sort()
1294 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
1295 self.are_keys_builtin.set(0)
1296 self.set_keys_type()
1297
1298 def load_keys_list(self, keyset_name):
1299 """Reload the list of action/key binding pairs for the active key set.
1300
1301 An action/key binding can be selected to change the key binding.
1302 """
1303 reselect = 0
1304 if self.list_bindings.curselection():
1305 reselect = 1
1306 list_index = self.list_bindings.index(ANCHOR)
1307 keyset = idleConf.GetKeySet(keyset_name)
1308 bind_names = list(keyset.keys())
1309 bind_names.sort()
1310 self.list_bindings.delete(0, END)
1311 for bind_name in bind_names:
1312 key = ' '.join(keyset[bind_name])
1313 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1314 if keyset_name in changes['keys']:
1315 # Handle any unsaved changes to this key set.
1316 if bind_name in changes['keys'][keyset_name]:
1317 key = changes['keys'][keyset_name][bind_name]
1318 self.list_bindings.insert(END, bind_name+' - '+key)
1319 if reselect:
1320 self.list_bindings.see(list_index)
1321 self.list_bindings.select_set(list_index)
1322 self.list_bindings.select_anchor(list_index)
1323
1324 def save_new_key_set(self, keyset_name, keyset):
1325 """Save a newly created core key set.
1326
1327 keyset_name - string, the name of the new key set
1328 keyset - dictionary containing the new key set
1329 """
1330 if not idleConf.userCfg['keys'].has_section(keyset_name):
1331 idleConf.userCfg['keys'].add_section(keyset_name)
1332 for event in keyset:
1333 value = keyset[event]
1334 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1335
1336 def delete_custom_keys(self):
1337 """Handle event to delete a custom key set.
1338
1339 Applying the delete deactivates the current configuration and
1340 reverts to the default. The custom key set is permanently
1341 deleted from the config file.
1342 """
1343 keyset_name=self.custom_keys.get()
1344 delmsg = 'Are you sure you wish to delete the key set %r ?'
1345 if not tkMessageBox.askyesno(
1346 'Delete Key Set', delmsg % keyset_name, parent=self):
1347 return
1348 self.deactivate_current_config()
1349 # Remove key set from changes, config, and file.
1350 changes.delete_section('keys', keyset_name)
1351 # Reload user key set list.
1352 item_list = idleConf.GetSectionList('user', 'keys')
1353 item_list.sort()
1354 if not item_list:
1355 self.radio_keys_custom['state'] = DISABLED
1356 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
1357 else:
1358 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1359 # Revert to default key set.
1360 self.are_keys_builtin.set(idleConf.defaultCfg['main']
1361 .Get('Keys', 'default'))
1362 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
1363 or idleConf.default_keys())
1364 # User can't back out of these changes, they must be applied now.
1365 changes.save_all()
1366 self.save_all_changed_extensions()
1367 self.activate_config_changes()
1368 self.set_keys_type()
1369
1370 def deactivate_current_config(self):
1371 """Remove current key bindings.
1372
1373 Iterate over window instances defined in parent and remove
1374 the keybindings.
1375 """
1376 # Before a config is saved, some cleanup of current
1377 # config must be done - remove the previous keybindings.
1378 win_instances = self.parent.instance_dict.keys()
1379 for instance in win_instances:
1380 instance.RemoveKeybindings()
1381
1382 def activate_config_changes(self):
1383 """Apply configuration changes to current windows.
1384
1385 Dynamically update the current parent window instances
1386 with some of the configuration changes.
1387 """
1388 win_instances = self.parent.instance_dict.keys()
1389 for instance in win_instances:
1390 instance.ResetColorizer()
1391 instance.ResetFont()
1392 instance.set_notabs_indentwidth()
1393 instance.ApplyKeybindings()
1394 instance.reset_help_menu_entries()
1395
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001396
csabellabac7d332017-06-26 17:46:26 -04001397 def create_page_general(self):
csabella7eb58832017-07-04 21:30:58 -04001398 """Return frame of widgets for General tab.
1399
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001400 Enable users to provisionally change general options. Function
1401 load_general_cfg intializes tk variables and helplist using
1402 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1403 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1404 set var autosave. Entry boxes win_width_int and win_height_int
1405 set var win_width and win_height. Setting var_name invokes the
csabella5b591542017-07-28 14:40:59 -04001406 default callback that adds option to changes.
csabella36329a42017-07-13 23:32:01 -04001407
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001408 Helplist: load_general_cfg loads list user_helplist with
1409 name, position pairs and copies names to listbox helplist.
1410 Clicking a name invokes help_source selected. Clicking
1411 button_helplist_name invokes helplist_item_name, which also
1412 changes user_helplist. These functions all call
1413 set_add_delete_state. All but load call update_help_changes to
1414 rewrite changes['main']['HelpFiles'].
csabella36329a42017-07-13 23:32:01 -04001415
1416 Widget Structure: (*) widgets bound to self
1417 frame
1418 frame_run: LabelFrame
1419 startup_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001420 (*)startup_editor_on: Radiobutton - startup_edit
1421 (*)startup_shell_on: Radiobutton - startup_edit
csabella36329a42017-07-13 23:32:01 -04001422 frame_save: LabelFrame
1423 run_save_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001424 (*)save_ask_on: Radiobutton - autosave
1425 (*)save_auto_on: Radiobutton - autosave
csabella36329a42017-07-13 23:32:01 -04001426 frame_win_size: LabelFrame
1427 win_size_title: Label
1428 win_width_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001429 (*)win_width_int: Entry - win_width
csabella36329a42017-07-13 23:32:01 -04001430 win_height_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001431 (*)win_height_int: Entry - win_height
csabella36329a42017-07-13 23:32:01 -04001432 frame_help: LabelFrame
1433 frame_helplist: Frame
1434 frame_helplist_buttons: Frame
1435 (*)button_helplist_edit
1436 (*)button_helplist_add
1437 (*)button_helplist_remove
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001438 (*)helplist: ListBox
csabella36329a42017-07-13 23:32:01 -04001439 scroll_helplist: Scrollbar
csabella7eb58832017-07-04 21:30:58 -04001440 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001441 parent = self.parent
csabella5b591542017-07-28 14:40:59 -04001442 self.startup_edit = tracers.add(
1443 IntVar(parent), ('main', 'General', 'editor-on-startup'))
1444 self.autosave = tracers.add(
1445 IntVar(parent), ('main', 'General', 'autosave'))
1446 self.win_width = tracers.add(
1447 StringVar(parent), ('main', 'EditorWindow', 'width'))
1448 self.win_height = tracers.add(
1449 StringVar(parent), ('main', 'EditorWindow', 'height'))
Terry Jan Reedy22405332014-07-30 19:24:32 -04001450
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001451 # Create widgets:
1452 # body.
csabellabac7d332017-06-26 17:46:26 -04001453 frame = self.tab_pages.pages['General'].frame
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001454 # body section frames.
csabellabac7d332017-06-26 17:46:26 -04001455 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001456 text=' Startup Preferences ')
csabellabac7d332017-06-26 17:46:26 -04001457 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
1458 text=' autosave Preferences ')
1459 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
1460 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001461 text=' Additional Help Sources ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001462 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001463 startup_title = Label(frame_run, text='At Startup')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001464 self.startup_editor_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001465 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001466 text="Open Edit Window")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001467 self.startup_shell_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001468 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001469 text='Open Shell Window')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001470 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001471 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001472 self.save_ask_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001473 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001474 text="Prompt to Save")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001475 self.save_auto_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001476 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001477 text='No Prompt')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001478 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001479 win_size_title = Label(
1480 frame_win_size, text='Initial Window Size (in characters)')
1481 win_width_title = Label(frame_win_size, text='Width')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001482 self.win_width_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001483 frame_win_size, textvariable=self.win_width, width=3)
1484 win_height_title = Label(frame_win_size, text='Height')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001485 self.win_height_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001486 frame_win_size, textvariable=self.win_height, width=3)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001487 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001488 frame_helplist = Frame(frame_help)
1489 frame_helplist_buttons = Frame(frame_helplist)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001490 self.helplist = Listbox(
csabellabac7d332017-06-26 17:46:26 -04001491 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001492 exportselection=FALSE)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001493 scroll_helplist = Scrollbar(frame_helplist)
1494 scroll_helplist['command'] = self.helplist.yview
1495 self.helplist['yscrollcommand'] = scroll_helplist.set
1496 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
csabellabac7d332017-06-26 17:46:26 -04001497 self.button_helplist_edit = Button(
1498 frame_helplist_buttons, text='Edit', state=DISABLED,
1499 width=8, command=self.helplist_item_edit)
1500 self.button_helplist_add = Button(
1501 frame_helplist_buttons, text='Add',
1502 width=8, command=self.helplist_item_add)
1503 self.button_helplist_remove = Button(
1504 frame_helplist_buttons, text='Remove', state=DISABLED,
1505 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001506
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001507 # Pack widgets:
1508 # body.
csabellabac7d332017-06-26 17:46:26 -04001509 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
1510 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
1511 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
1512 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001513 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001514 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001515 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1516 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1517 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001518 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001519 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1520 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1521 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001522 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001523 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001524 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001525 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001526 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001527 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001528 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
1529 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1530 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001531 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
csabellabac7d332017-06-26 17:46:26 -04001532 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
1533 self.button_helplist_add.pack(side=TOP, anchor=W)
1534 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001535
Steven M. Gava952d0a52001-08-03 04:43:44 +00001536 return frame
1537
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001538 def load_general_cfg(self):
1539 "Load current configuration settings for the general options."
1540 # Set startup state.
1541 self.startup_edit.set(idleConf.GetOption(
1542 'main', 'General', 'editor-on-startup', default=0, type='bool'))
1543 # Set autosave state.
1544 self.autosave.set(idleConf.GetOption(
1545 'main', 'General', 'autosave', default=0, type='bool'))
1546 # Set initial window size.
1547 self.win_width.set(idleConf.GetOption(
1548 'main', 'EditorWindow', 'width', type='int'))
1549 self.win_height.set(idleConf.GetOption(
1550 'main', 'EditorWindow', 'height', type='int'))
1551 # Set additional help sources.
1552 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1553 self.helplist.delete(0, 'end')
1554 for help_item in self.user_helplist:
1555 self.helplist.insert(END, help_item[0])
1556 self.set_add_delete_state()
1557
1558 def var_changed_startup_edit(self, *params):
1559 "Store change to toggle for starting IDLE in the editor or shell."
1560 value = self.startup_edit.get()
1561 changes.add_option('main', 'General', 'editor-on-startup', value)
1562
1563 def var_changed_autosave(self, *params):
1564 "Store change to autosave."
1565 value = self.autosave.get()
1566 changes.add_option('main', 'General', 'autosave', value)
1567
1568 def var_changed_win_width(self, *params):
1569 "Store change to window width."
1570 value = self.win_width.get()
1571 changes.add_option('main', 'EditorWindow', 'width', value)
1572
1573 def var_changed_win_height(self, *params):
1574 "Store change to window height."
1575 value = self.win_height.get()
1576 changes.add_option('main', 'EditorWindow', 'height', value)
1577
1578 def help_source_selected(self, event):
1579 "Handle event for selecting additional help."
1580 self.set_add_delete_state()
1581
1582 def set_add_delete_state(self):
1583 "Toggle the state for the help list buttons based on list entries."
1584 if self.helplist.size() < 1: # No entries in list.
1585 self.button_helplist_edit['state'] = DISABLED
1586 self.button_helplist_remove['state'] = DISABLED
1587 else: # Some entries.
1588 if self.helplist.curselection(): # There currently is a selection.
1589 self.button_helplist_edit['state'] = NORMAL
1590 self.button_helplist_remove['state'] = NORMAL
1591 else: # There currently is not a selection.
1592 self.button_helplist_edit['state'] = DISABLED
1593 self.button_helplist_remove['state'] = DISABLED
1594
1595 def helplist_item_add(self):
1596 """Handle add button for the help list.
1597
1598 Query for name and location of new help sources and add
1599 them to the list.
1600 """
1601 help_source = HelpSource(self, 'New Help Source').result
1602 if help_source:
1603 self.user_helplist.append(help_source)
1604 self.helplist.insert(END, help_source[0])
1605 self.update_help_changes()
1606
1607 def helplist_item_edit(self):
1608 """Handle edit button for the help list.
1609
1610 Query with existing help source information and update
1611 config if the values are changed.
1612 """
1613 item_index = self.helplist.index(ANCHOR)
1614 help_source = self.user_helplist[item_index]
1615 new_help_source = HelpSource(
1616 self, 'Edit Help Source',
1617 menuitem=help_source[0],
1618 filepath=help_source[1],
1619 ).result
1620 if new_help_source and new_help_source != help_source:
1621 self.user_helplist[item_index] = new_help_source
1622 self.helplist.delete(item_index)
1623 self.helplist.insert(item_index, new_help_source[0])
1624 self.update_help_changes()
1625 self.set_add_delete_state() # Selected will be un-selected
1626
1627 def helplist_item_remove(self):
1628 """Handle remove button for the help list.
1629
1630 Delete the help list item from config.
1631 """
1632 item_index = self.helplist.index(ANCHOR)
1633 del(self.user_helplist[item_index])
1634 self.helplist.delete(item_index)
1635 self.update_help_changes()
1636 self.set_add_delete_state()
1637
1638 def update_help_changes(self):
1639 "Clear and rebuild the HelpFiles section in changes"
1640 changes['main']['HelpFiles'] = {}
1641 for num in range(1, len(self.user_helplist) + 1):
1642 changes.add_option(
1643 'main', 'HelpFiles', str(num),
1644 ';'.join(self.user_helplist[num-1][:2]))
1645
1646
csabellabac7d332017-06-26 17:46:26 -04001647 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001648 """Part of the config dialog used for configuring IDLE extensions.
1649
1650 This code is generic - it works for any and all IDLE extensions.
1651
1652 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001653 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001654 GUI interface to change the configuration values, and saves the
1655 changes using idleConf.
1656
1657 Not all changes take effect immediately - some may require restarting IDLE.
1658 This depends on each extension's implementation.
1659
1660 All values are treated as text, and it is up to the user to supply
1661 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001662 which are boolean, and can be toggled with a True/False button.
csabella36329a42017-07-13 23:32:01 -04001663
1664 Methods:
1665 load_extentions:
1666 extension_selected: Handle selection from list.
1667 create_extension_frame: Hold widgets for one extension.
1668 set_extension_value: Set in userCfg['extensions'].
1669 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001670 """
1671 parent = self.parent
csabellabac7d332017-06-26 17:46:26 -04001672 frame = self.tab_pages.pages['Extensions'].frame
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001673 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1674 self.ext_userCfg = idleConf.userCfg['extensions']
1675 self.is_int = self.register(is_int)
1676 self.load_extensions()
csabella7eb58832017-07-04 21:30:58 -04001677 # Create widgets - a listbox shows all available extensions, with the
1678 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001679 self.extension_names = StringVar(self)
1680 frame.rowconfigure(0, weight=1)
1681 frame.columnconfigure(2, weight=1)
1682 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1683 selectmode='browse')
1684 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1685 scroll = Scrollbar(frame, command=self.extension_list.yview)
1686 self.extension_list.yscrollcommand=scroll.set
1687 self.details_frame = LabelFrame(frame, width=250, height=250)
1688 self.extension_list.grid(column=0, row=0, sticky='nws')
1689 scroll.grid(column=1, row=0, sticky='ns')
1690 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1691 frame.configure(padx=10, pady=10)
1692 self.config_frame = {}
1693 self.current_extension = None
1694
1695 self.outerframe = self # TEMPORARY
1696 self.tabbed_page_set = self.extension_list # TEMPORARY
1697
csabella7eb58832017-07-04 21:30:58 -04001698 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001699 ext_names = ''
1700 for ext_name in sorted(self.extensions):
1701 self.create_extension_frame(ext_name)
1702 ext_names = ext_names + '{' + ext_name + '} '
1703 self.extension_names.set(ext_names)
1704 self.extension_list.selection_set(0)
1705 self.extension_selected(None)
1706
1707 def load_extensions(self):
1708 "Fill self.extensions with data from the default and user configs."
1709 self.extensions = {}
1710 for ext_name in idleConf.GetExtensions(active_only=False):
1711 self.extensions[ext_name] = []
1712
1713 for ext_name in self.extensions:
1714 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1715
csabella7eb58832017-07-04 21:30:58 -04001716 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001717 enables = [opt_name for opt_name in opt_list
1718 if opt_name.startswith('enable')]
1719 for opt_name in enables:
1720 opt_list.remove(opt_name)
1721 opt_list = enables + opt_list
1722
1723 for opt_name in opt_list:
1724 def_str = self.ext_defaultCfg.Get(
1725 ext_name, opt_name, raw=True)
1726 try:
1727 def_obj = {'True':True, 'False':False}[def_str]
1728 opt_type = 'bool'
1729 except KeyError:
1730 try:
1731 def_obj = int(def_str)
1732 opt_type = 'int'
1733 except ValueError:
1734 def_obj = def_str
1735 opt_type = None
1736 try:
1737 value = self.ext_userCfg.Get(
1738 ext_name, opt_name, type=opt_type, raw=True,
1739 default=def_obj)
csabella7eb58832017-07-04 21:30:58 -04001740 except ValueError: # Need this until .Get fixed.
1741 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001742 var = StringVar(self)
1743 var.set(str(value))
1744
1745 self.extensions[ext_name].append({'name': opt_name,
1746 'type': opt_type,
1747 'default': def_str,
1748 'value': value,
1749 'var': var,
1750 })
1751
1752 def extension_selected(self, event):
csabella7eb58832017-07-04 21:30:58 -04001753 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001754 newsel = self.extension_list.curselection()
1755 if newsel:
1756 newsel = self.extension_list.get(newsel)
1757 if newsel is None or newsel != self.current_extension:
1758 if self.current_extension:
1759 self.details_frame.config(text='')
1760 self.config_frame[self.current_extension].grid_forget()
1761 self.current_extension = None
1762 if newsel:
1763 self.details_frame.config(text=newsel)
1764 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1765 self.current_extension = newsel
1766
1767 def create_extension_frame(self, ext_name):
1768 """Create a frame holding the widgets to configure one extension"""
1769 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1770 self.config_frame[ext_name] = f
1771 entry_area = f.interior
csabella7eb58832017-07-04 21:30:58 -04001772 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001773 for row, opt in enumerate(self.extensions[ext_name]):
csabella7eb58832017-07-04 21:30:58 -04001774 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001775 label = Label(entry_area, text=opt['name'])
1776 label.grid(row=row, column=0, sticky=NW)
1777 var = opt['var']
1778 if opt['type'] == 'bool':
1779 Checkbutton(entry_area, textvariable=var, variable=var,
1780 onvalue='True', offvalue='False',
1781 indicatoron=FALSE, selectcolor='', width=8
1782 ).grid(row=row, column=1, sticky=W, padx=7)
1783 elif opt['type'] == 'int':
1784 Entry(entry_area, textvariable=var, validate='key',
1785 validatecommand=(self.is_int, '%P')
1786 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1787
1788 else:
1789 Entry(entry_area, textvariable=var
1790 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1791 return
1792
1793 def set_extension_value(self, section, opt):
csabella7eb58832017-07-04 21:30:58 -04001794 """Return True if the configuration was added or changed.
1795
1796 If the value is the same as the default, then remove it
1797 from user config file.
1798 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001799 name = opt['name']
1800 default = opt['default']
1801 value = opt['var'].get().strip() or default
1802 opt['var'].set(value)
1803 # if self.defaultCfg.has_section(section):
csabella7eb58832017-07-04 21:30:58 -04001804 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001805 if (value == default):
1806 return self.ext_userCfg.RemoveOption(section, name)
csabella7eb58832017-07-04 21:30:58 -04001807 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001808 return self.ext_userCfg.SetOption(section, name, value)
1809
1810 def save_all_changed_extensions(self):
csabella36329a42017-07-13 23:32:01 -04001811 """Save configuration changes to the user config file.
1812
1813 Attributes accessed:
1814 extensions
1815
1816 Methods:
1817 set_extension_value
1818 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001819 has_changes = False
1820 for ext_name in self.extensions:
1821 options = self.extensions[ext_name]
1822 for opt in options:
1823 if self.set_extension_value(ext_name, opt):
1824 has_changes = True
1825 if has_changes:
1826 self.ext_userCfg.Save()
1827
1828
csabella45bf7232017-07-26 19:09:58 -04001829class VarTrace:
1830 """Maintain Tk variables trace state."""
1831
1832 def __init__(self):
1833 """Store Tk variables and callbacks.
1834
1835 untraced: List of tuples (var, callback)
1836 that do not have the callback attached
1837 to the Tk var.
1838 traced: List of tuples (var, callback) where
1839 that callback has been attached to the var.
1840 """
1841 self.untraced = []
1842 self.traced = []
1843
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04001844 def clear(self):
1845 "Clear lists (for tests)."
1846 self.untraced.clear()
1847 self.traced.clear()
1848
csabella45bf7232017-07-26 19:09:58 -04001849 def add(self, var, callback):
1850 """Add (var, callback) tuple to untraced list.
1851
1852 Args:
1853 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04001854 callback: Either function name to be used as a callback
1855 or a tuple with IdleConf config-type, section, and
1856 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04001857
1858 Return:
1859 Tk variable instance.
1860 """
1861 if isinstance(callback, tuple):
1862 callback = self.make_callback(var, callback)
1863 self.untraced.append((var, callback))
1864 return var
1865
1866 @staticmethod
1867 def make_callback(var, config):
1868 "Return default callback function to add values to changes instance."
1869 def default_callback(*params):
1870 "Add config values to changes instance."
1871 changes.add_option(*config, var.get())
1872 return default_callback
1873
1874 def attach(self):
1875 "Attach callback to all vars that are not traced."
1876 while self.untraced:
1877 var, callback = self.untraced.pop()
1878 var.trace_add('write', callback)
1879 self.traced.append((var, callback))
1880
1881 def detach(self):
1882 "Remove callback from traced vars."
1883 while self.traced:
1884 var, callback = self.traced.pop()
1885 var.trace_remove('write', var.trace_info()[0][1])
1886 self.untraced.append((var, callback))
1887
1888
csabella5b591542017-07-28 14:40:59 -04001889tracers = VarTrace()
1890
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001891help_common = '''\
1892When you click either the Apply or Ok buttons, settings in this
1893dialog that are different from IDLE's default are saved in
1894a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001895these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001896machine. Some do not take affect until IDLE is restarted.
1897[Cancel] only cancels changes made since the last save.
1898'''
1899help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001900 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001901Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001902The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001903be used with older IDLE releases if it is saved as a custom
1904theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001905''',
1906 'Keys': '''
1907Keys:
1908The IDLE Modern Unix key set is new in June 2016. It can only
1909be used with older IDLE releases if it is saved as a custom
1910key set, with a different name.
1911''',
wohlgangerfae2c352017-06-27 21:36:23 -05001912 'Extensions': '''
1913Extensions:
1914
1915Autocomplete: Popupwait is milleseconds to wait after key char, without
1916cursor movement, before popping up completion box. Key char is '.' after
1917identifier or a '/' (or '\\' on Windows) within a string.
1918
1919FormatParagraph: Max-width is max chars in lines after re-formatting.
1920Use with paragraphs in both strings and comment blocks.
1921
1922ParenMatch: Style indicates what is highlighted when closer is entered:
1923'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1924'expression' (default) - also everything in between. Flash-delay is how
1925long to highlight if cursor is not moved (0 means forever).
1926'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001927}
1928
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001929
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001930def is_int(s):
1931 "Return 's is blank or represents an int'"
1932 if not s:
1933 return True
1934 try:
1935 int(s)
1936 return True
1937 except ValueError:
1938 return False
1939
1940
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001941class VerticalScrolledFrame(Frame):
1942 """A pure Tkinter vertically scrollable frame.
1943
1944 * Use the 'interior' attribute to place widgets inside the scrollable frame
1945 * Construct and pack/place/grid normally
1946 * This frame only allows vertical scrolling
1947 """
1948 def __init__(self, parent, *args, **kw):
1949 Frame.__init__(self, parent, *args, **kw)
1950
csabella7eb58832017-07-04 21:30:58 -04001951 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001952 vscrollbar = Scrollbar(self, orient=VERTICAL)
1953 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1954 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001955 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001956 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1957 vscrollbar.config(command=canvas.yview)
1958
csabella7eb58832017-07-04 21:30:58 -04001959 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001960 canvas.xview_moveto(0)
1961 canvas.yview_moveto(0)
1962
csabella7eb58832017-07-04 21:30:58 -04001963 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001964 self.interior = interior = Frame(canvas)
1965 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1966
csabella7eb58832017-07-04 21:30:58 -04001967 # Track changes to the canvas and frame width and sync them,
1968 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001969 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04001970 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001971 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1972 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001973 interior.bind('<Configure>', _configure_interior)
1974
1975 def _configure_canvas(event):
1976 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04001977 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001978 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1979 canvas.bind('<Configure>', _configure_canvas)
1980
1981 return
1982
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001983
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00001984if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04001985 import unittest
1986 unittest.main('idlelib.idle_test.test_configdialog',
1987 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04001988 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04001989 run(ConfigDialog)