blob: 7c5f3c8ca4e376f81ca9fe6404b032f3c596e214 [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"""
terryjreedy938e7382017-06-26 20:48:39 -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,
terryjreedy7ab33422017-07-09 19:26:32 -040017 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
Terry Jan Reedy8364fef2017-07-29 01:28:05 -040018from tkinter.ttk import Notebook, Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000019import tkinter.colorchooser as tkColorChooser
20import tkinter.font as tkFont
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021import tkinter.messagebox as tkMessageBox
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000022
terryjreedyedc03422017-07-07 16:37:39 -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
terryjreedyedc03422017-07-07 16:37:39 -040031changes = ConfigChanges()
32
Terry Jan Reedy02f88d22017-07-28 15:42:43 -040033
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000034class ConfigDialog(Toplevel):
terryjreedye5bb1122017-07-05 00:54:55 -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):
terryjreedye5bb1122017-07-05 00:54:55 -040039 """Show the tabbed dialog for user configuration.
40
terryjreedy9a09c662017-07-13 23:53:30 -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 = {}
terryjreedy42abf7f2017-07-13 22:24:55 -040057 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')
terryjreedy938e7382017-06-26 20:48:39 -040062 x = parent.winfo_rootx() + 20
63 y = parent.winfo_rooty() + (30 if not _htest else 150)
64 self.geometry(f'+{x}+{y}')
terryjreedye5bb1122017-07-05 00:54:55 -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.
terryjreedy938e7382017-06-26 20:48:39 -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)
terryjreedy938e7382017-06-26 20:48:39 -040071 self.protocol("WM_DELETE_WINDOW", self.cancel)
Terry Jan Reedy75822262017-07-30 15:00:50 -040072 self.fontpage.fontlist.focus_set()
terryjreedye5bb1122017-07-05 00:54:55 -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
terryjreedy938e7382017-06-26 20:48:39 -040078 self.load_configs()
Terry Jan Reedy02f88d22017-07-28 15:42:43 -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:
terryjreedy42abf7f2017-07-13 22:24:55 -040083 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
Terry Jan Reedy02f88d22017-07-28 15:42:43 -040087
terryjreedy938e7382017-06-26 20:48:39 -040088 def create_widgets(self):
terryjreedy9a09c662017-07-13 23:53:30 -040089 """Create and place widgets for tabbed dialog.
90
91 Widgets Bound to self:
Terry Jan Reedy8c4e5be2017-07-30 19:02:51 -040092 note: Notebook
93 highpage: self.create_page_highlight
94 fontpage: FontPage
95 keyspage: self.create_page_keys
96 genpage: GenPage
97 extpageL self.create_page_extensions
terryjreedy9a09c662017-07-13 23:53:30 -040098
99 Methods:
terryjreedy9a09c662017-07-13 23:53:30 -0400100 create_action_buttons
101 load_configs: Load pages except for extensions.
terryjreedy9a09c662017-07-13 23:53:30 -0400102 activate_config_changes: Tell editors to reload.
103 """
Terry Jan Reedy8364fef2017-07-29 01:28:05 -0400104 self.note = note = Notebook(self, width=450, height=450)
Terry Jan Reedy75822262017-07-30 15:00:50 -0400105 self.highpage = self.create_page_highlight()
106 self.fontpage = FontPage(note, self.highpage)
107 self.keyspage = self.create_page_keys()
Terry Jan Reedy8c4e5be2017-07-30 19:02:51 -0400108 self.genpage = GenPage(note)
Terry Jan Reedy75822262017-07-30 15:00:50 -0400109 self.extpage = self.create_page_extensions()
110 note.add(self.fontpage, text='Fonts/Tabs')
111 note.add(self.highpage, text='Highlights')
112 note.add(self.keyspage, text=' Keys ')
113 note.add(self.genpage, text=' General ')
114 note.add(self.extpage, text='Extensions')
Terry Jan Reedy8364fef2017-07-29 01:28:05 -0400115 note.enable_traversal()
116 note.pack(side=TOP, expand=TRUE, fill=BOTH)
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400117 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400118
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400119 def load_configs(self):
120 """Load configuration for each page.
121
122 Load configuration from default and user config files and populate
123 the widgets on the config dialog pages.
124
125 Methods:
126 load_font_cfg
127 load_tab_cfg
128 load_theme_cfg
129 load_key_cfg
130 load_general_cfg
131 """
Terry Jan Reedy75822262017-07-30 15:00:50 -0400132 #self.load_font_cfg()
133 #self.load_tab_cfg()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400134 self.load_theme_cfg()
135 self.load_key_cfg()
Terry Jan Reedy8c4e5be2017-07-30 19:02:51 -0400136 # self.load_general_cfg()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400137 # note: extension page handled separately
138
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400139 def create_action_buttons(self):
terryjreedy9a09c662017-07-13 23:53:30 -0400140 """Return frame of action buttons for dialog.
141
142 Methods:
143 ok
144 apply
145 cancel
146 help
147
148 Widget Structure:
149 outer: Frame
150 buttons: Frame
151 (no assignment): Button (ok)
152 (no assignment): Button (apply)
153 (no assignment): Button (cancel)
154 (no assignment): Button (help)
155 (no assignment): Frame
156 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400157 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400158 # Changing the default padding on OSX results in unreadable
terryjreedye5bb1122017-07-05 00:54:55 -0400159 # text in the buttons.
terryjreedy938e7382017-06-26 20:48:39 -0400160 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000161 else:
terryjreedy938e7382017-06-26 20:48:39 -0400162 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400163 outer = Frame(self, pady=2)
164 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400165 for txt, cmd in (
terryjreedy938e7382017-06-26 20:48:39 -0400166 ('Ok', self.ok),
167 ('Apply', self.apply),
168 ('Cancel', self.cancel),
169 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400170 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
terryjreedy938e7382017-06-26 20:48:39 -0400171 **padding_args).pack(side=LEFT, padx=5)
terryjreedye5bb1122017-07-05 00:54:55 -0400172 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400173 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
174 buttons.pack(side=BOTTOM)
175 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400176
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400177 def ok(self):
178 """Apply config changes, then dismiss dialog.
179
180 Methods:
181 apply
182 destroy: inherited
183 """
184 self.apply()
185 self.destroy()
186
187 def apply(self):
188 """Apply config changes and leave dialog open.
189
190 Methods:
191 deactivate_current_config
192 save_all_changed_extensions
193 activate_config_changes
194 """
195 self.deactivate_current_config()
196 changes.save_all()
197 self.save_all_changed_extensions()
198 self.activate_config_changes()
199
200 def cancel(self):
201 """Dismiss config dialog.
202
203 Methods:
204 destroy: inherited
205 """
206 self.destroy()
207
208 def help(self):
209 """Create textview for config dialog help.
210
211 Attrbutes accessed:
212 tab_pages
213
214 Methods:
215 view_text: Method from textview module.
216 """
217 page = self.tab_pages._current_page
218 view_text(self, title='Help for IDLE preferences',
219 text=help_common+help_pages.get(page, ''))
220
Terry Jan Reedy1daeb252017-07-24 02:50:28 -0400221
terryjreedy938e7382017-06-26 20:48:39 -0400222 def create_page_highlight(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400223 """Return frame of widgets for Highlighting tab.
224
terryjreedy9a09c662017-07-13 23:53:30 -0400225 Tk Variables:
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400226 color: Color of selected target.
terryjreedye5bb1122017-07-05 00:54:55 -0400227 builtin_theme: Menu variable for built-in theme.
228 custom_theme: Menu variable for custom theme.
229 fg_bg_toggle: Toggle for foreground/background color.
terryjreedy9a09c662017-07-13 23:53:30 -0400230 Note: this has no callback.
terryjreedye5bb1122017-07-05 00:54:55 -0400231 is_builtin_theme: Selector for built-in or custom theme.
232 highlight_target: Menu variable for the highlight tag target.
terryjreedy9a09c662017-07-13 23:53:30 -0400233
234 Instance Data Attributes:
235 theme_elements: Dictionary of tags for text highlighting.
236 The key is the display name and the value is a tuple of
237 (tag name, display sort order).
238
239 Methods [attachment]:
240 load_theme_cfg: Load current highlight colors.
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400241 get_color: Invoke colorchooser [button_set_color].
242 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
terryjreedy9a09c662017-07-13 23:53:30 -0400243 set_highlight_target: set fg_bg_toggle, set_color_sample().
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400244 set_color_sample: Set frame background to target.
245 on_new_color_set: Set new color and add option.
terryjreedy9a09c662017-07-13 23:53:30 -0400246 paint_theme_sample: Recolor sample.
247 get_new_theme_name: Get from popup.
248 create_new_theme: Combine theme with changes and save.
249 save_as_new_theme: Save [button_save_custom_theme].
250 set_theme_type: Command for [is_builtin_theme].
251 delete_custom_theme: Ativate default [button_delete_custom_theme].
252 save_new_theme: Save to userCfg['theme'] (is function).
253
Terry Jan Reedya3145902017-08-14 21:45:02 -0400254 Widgets of highlights page frame: (*) widgets bound to self
255 frame_custom: LabelFrame
256 (*)highlight_sample: Text
257 (*)frame_color_set: Frame
258 button_set_color: Button
259 (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
260 frame_fg_bg_toggle: Frame
261 (*)radio_fg: Radiobutton - fg_bg_toggle
262 (*)radio_bg: Radiobutton - fg_bg_toggle
263 button_save_custom_theme: Button
264 frame_theme: LabelFrame
265 theme_type_title: Label
266 (*)radio_theme_builtin: Radiobutton - is_builtin_theme
267 (*)radio_theme_custom: Radiobutton - is_builtin_theme
268 (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
269 (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
270 (*)button_delete_custom_theme: Button
271 (*)new_custom_theme: Label
terryjreedye5bb1122017-07-05 00:54:55 -0400272 """
terryjreedy9a09c662017-07-13 23:53:30 -0400273 self.theme_elements={
274 'Normal Text': ('normal', '00'),
275 'Python Keywords': ('keyword', '01'),
276 'Python Definitions': ('definition', '02'),
277 'Python Builtins': ('builtin', '03'),
278 'Python Comments': ('comment', '04'),
279 'Python Strings': ('string', '05'),
280 'Selected Text': ('hilite', '06'),
281 'Found Text': ('hit', '07'),
282 'Cursor': ('cursor', '08'),
283 'Editor Breakpoint': ('break', '09'),
284 'Shell Normal Text': ('console', '10'),
285 'Shell Error Text': ('error', '11'),
286 'Shell Stdout Text': ('stdout', '12'),
287 'Shell Stderr Text': ('stderr', '13'),
288 }
Terry Jan Reedy22405332014-07-30 19:24:32 -0400289 parent = self.parent
Terry Jan Reedy02f88d22017-07-28 15:42:43 -0400290 self.builtin_theme = tracers.add(
291 StringVar(parent), self.var_changed_builtin_theme)
292 self.custom_theme = tracers.add(
293 StringVar(parent), self.var_changed_custom_theme)
terryjreedy938e7382017-06-26 20:48:39 -0400294 self.fg_bg_toggle = BooleanVar(parent)
Terry Jan Reedy02f88d22017-07-28 15:42:43 -0400295 self.color = tracers.add(
296 StringVar(parent), self.var_changed_color)
297 self.is_builtin_theme = tracers.add(
298 BooleanVar(parent), self.var_changed_is_builtin_theme)
299 self.highlight_target = tracers.add(
300 StringVar(parent), self.var_changed_highlight_target)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400301
Terry Jan Reedy8364fef2017-07-29 01:28:05 -0400302 # Widget creation:
303 # body frame and section frames
304 frame = Frame(self.note)
terryjreedy938e7382017-06-26 20:48:39 -0400305 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400306 text=' Custom Highlighting ')
terryjreedy938e7382017-06-26 20:48:39 -0400307 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400308 text=' Highlighting Theme ')
terryjreedy938e7382017-06-26 20:48:39 -0400309 #frame_custom
Terry Jan Reedy75822262017-07-30 15:00:50 -0400310 text = self.highlight_sample = frame.highlight_sample = Text(
terryjreedy938e7382017-06-26 20:48:39 -0400311 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy8364fef2017-07-29 01:28:05 -0400312 font=('courier', 12, ''), cursor='hand2', width=21, height=13,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400313 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Terry Jan Reedy04864b42017-07-22 00:56:18 -0400314 text=self.highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400315 text.bind('<Double-Button-1>', lambda e: 'break')
316 text.bind('<B1-Motion>', lambda e: 'break')
Terry Jan Reedy8364fef2017-07-29 01:28:05 -0400317 text_and_tags=(('\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400318 ('#you can click here', 'comment'), ('\n', 'normal'),
319 ('#to choose items', 'comment'), ('\n', 'normal'),
320 ('def', 'keyword'), (' ', 'normal'),
321 ('func', 'definition'), ('(param):\n ', 'normal'),
322 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
323 ("'string'", 'string'), ('\n var1 = ', 'normal'),
324 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
325 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
326 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400327 ('None', 'keyword'), (')\n', 'normal'),
328 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400329 (' error ', 'error'), (' ', 'normal'),
330 ('cursor |', 'cursor'), ('\n ', 'normal'),
331 ('shell', 'console'), (' ', 'normal'),
332 ('stdout', 'stdout'), (' ', 'normal'),
Terry Jan Reedy8364fef2017-07-29 01:28:05 -0400333 ('stderr', 'stderr'), ('\n\n', 'normal'))
terryjreedy938e7382017-06-26 20:48:39 -0400334 for texttag in text_and_tags:
335 text.insert(END, texttag[0], texttag[1])
336 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400337 def tem(event, elem=element):
terryjreedy938e7382017-06-26 20:48:39 -0400338 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400339 text.tag_bind(
terryjreedy938e7382017-06-26 20:48:39 -0400340 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Terry Jan Reedy0c4c6512017-07-26 21:41:26 -0400341 text['state'] = DISABLED
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400342 self.frame_color_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
terryjreedy938e7382017-06-26 20:48:39 -0400343 frame_fg_bg_toggle = Frame(frame_custom)
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400344 button_set_color = Button(
345 self.frame_color_set, text='Choose Color for :',
346 command=self.get_color, highlightthickness=0)
terryjreedy938e7382017-06-26 20:48:39 -0400347 self.opt_menu_highlight_target = DynOptionMenu(
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400348 self.frame_color_set, self.highlight_target, None,
terryjreedy938e7382017-06-26 20:48:39 -0400349 highlightthickness=0) #, command=self.set_highlight_targetBinding
350 self.radio_fg = Radiobutton(
351 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400352 text='Foreground', command=self.set_color_sample_binding)
terryjreedy938e7382017-06-26 20:48:39 -0400353 self.radio_bg=Radiobutton(
354 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400355 text='Background', command=self.set_color_sample_binding)
terryjreedy938e7382017-06-26 20:48:39 -0400356 self.fg_bg_toggle.set(1)
357 button_save_custom_theme = Button(
358 frame_custom, text='Save as New Custom Theme',
359 command=self.save_as_new_theme)
360 #frame_theme
361 theme_type_title = Label(frame_theme, text='Select : ')
362 self.radio_theme_builtin = Radiobutton(
363 frame_theme, variable=self.is_builtin_theme, value=1,
364 command=self.set_theme_type, text='a Built-in Theme')
365 self.radio_theme_custom = Radiobutton(
366 frame_theme, variable=self.is_builtin_theme, value=0,
367 command=self.set_theme_type, text='a Custom Theme')
368 self.opt_menu_theme_builtin = DynOptionMenu(
369 frame_theme, self.builtin_theme, None, command=None)
370 self.opt_menu_theme_custom=DynOptionMenu(
371 frame_theme, self.custom_theme, None, command=None)
372 self.button_delete_custom_theme=Button(
373 frame_theme, text='Delete Custom Theme',
374 command=self.delete_custom_theme)
375 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400376
Steven M. Gava952d0a52001-08-03 04:43:44 +0000377 ##widget packing
378 #body
terryjreedy938e7382017-06-26 20:48:39 -0400379 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
380 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
381 #frame_custom
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400382 self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
terryjreedy938e7382017-06-26 20:48:39 -0400383 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Terry Jan Reedy04864b42017-07-22 00:56:18 -0400384 self.highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400385 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedyac5c1e22017-07-21 01:29:09 -0400386 button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
terryjreedy938e7382017-06-26 20:48:39 -0400387 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400388 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
terryjreedy938e7382017-06-26 20:48:39 -0400389 self.radio_fg.pack(side=LEFT, anchor=E)
390 self.radio_bg.pack(side=RIGHT, anchor=W)
391 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
392 #frame_theme
393 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
394 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
395 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
396 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
397 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
398 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500399 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000400 return frame
401
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400402 def load_theme_cfg(self):
403 """Load current configuration settings for the theme options.
404
405 Based on the is_builtin_theme toggle, the theme is set as
406 either builtin or custom and the initial widget values
407 reflect the current settings from idleConf.
408
409 Attributes updated:
410 is_builtin_theme: Set from idleConf.
411 opt_menu_theme_builtin: List of default themes from idleConf.
412 opt_menu_theme_custom: List of custom themes from idleConf.
413 radio_theme_custom: Disabled if there are no custom themes.
414 custom_theme: Message with additional information.
415 opt_menu_highlight_target: Create menu from self.theme_elements.
416
417 Methods:
418 set_theme_type
419 paint_theme_sample
420 set_highlight_target
421 """
422 # Set current theme type radiobutton.
423 self.is_builtin_theme.set(idleConf.GetOption(
424 'main', 'Theme', 'default', type='bool', default=1))
425 # Set current theme.
426 current_option = idleConf.CurrentTheme()
427 # Load available theme option menus.
428 if self.is_builtin_theme.get(): # Default theme selected.
429 item_list = idleConf.GetSectionList('default', 'highlight')
430 item_list.sort()
431 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
432 item_list = idleConf.GetSectionList('user', 'highlight')
433 item_list.sort()
434 if not item_list:
435 self.radio_theme_custom['state'] = DISABLED
436 self.custom_theme.set('- no custom themes -')
437 else:
438 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
439 else: # User theme selected.
440 item_list = idleConf.GetSectionList('user', 'highlight')
441 item_list.sort()
442 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
443 item_list = idleConf.GetSectionList('default', 'highlight')
444 item_list.sort()
445 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
446 self.set_theme_type()
447 # Load theme element option menu.
448 theme_names = list(self.theme_elements.keys())
449 theme_names.sort(key=lambda x: self.theme_elements[x][1])
450 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
451 self.paint_theme_sample()
452 self.set_highlight_target()
453
454 def var_changed_builtin_theme(self, *params):
455 """Process new builtin theme selection.
456
457 Add the changed theme's name to the changed_items and recreate
458 the sample with the values from the selected theme.
459 """
460 old_themes = ('IDLE Classic', 'IDLE New')
461 value = self.builtin_theme.get()
462 if value not in old_themes:
463 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
464 changes.add_option('main', 'Theme', 'name', old_themes[0])
465 changes.add_option('main', 'Theme', 'name2', value)
466 self.new_custom_theme.config(text='New theme, see Help',
467 fg='#500000')
468 else:
469 changes.add_option('main', 'Theme', 'name', value)
470 changes.add_option('main', 'Theme', 'name2', '')
471 self.new_custom_theme.config(text='', fg='black')
472 self.paint_theme_sample()
473
474 def var_changed_custom_theme(self, *params):
475 """Process new custom theme selection.
476
477 If a new custom theme is selected, add the name to the
478 changed_items and apply the theme to the sample.
479 """
480 value = self.custom_theme.get()
481 if value != '- no custom themes -':
482 changes.add_option('main', 'Theme', 'name', value)
483 self.paint_theme_sample()
484
485 def var_changed_is_builtin_theme(self, *params):
486 """Process toggle between builtin and custom theme.
487
488 Update the default toggle value and apply the newly
489 selected theme type.
490 """
491 value = self.is_builtin_theme.get()
492 changes.add_option('main', 'Theme', 'default', value)
493 if value:
494 self.var_changed_builtin_theme()
495 else:
496 self.var_changed_custom_theme()
497
498 def var_changed_color(self, *params):
499 "Process change to color choice."
500 self.on_new_color_set()
501
502 def var_changed_highlight_target(self, *params):
503 "Process selection of new target tag for highlighting."
504 self.set_highlight_target()
505
506 def set_theme_type(self):
507 """Set available screen options based on builtin or custom theme.
508
509 Attributes accessed:
510 is_builtin_theme
511
512 Attributes updated:
513 opt_menu_theme_builtin
514 opt_menu_theme_custom
515 button_delete_custom_theme
516 radio_theme_custom
517
518 Called from:
519 handler for radio_theme_builtin and radio_theme_custom
520 delete_custom_theme
521 create_new_theme
522 load_theme_cfg
523 """
524 if self.is_builtin_theme.get():
525 self.opt_menu_theme_builtin['state'] = NORMAL
526 self.opt_menu_theme_custom['state'] = DISABLED
527 self.button_delete_custom_theme['state'] = DISABLED
528 else:
529 self.opt_menu_theme_builtin['state'] = DISABLED
530 self.radio_theme_custom['state'] = NORMAL
531 self.opt_menu_theme_custom['state'] = NORMAL
532 self.button_delete_custom_theme['state'] = NORMAL
533
534 def get_color(self):
535 """Handle button to select a new color for the target tag.
536
537 If a new color is selected while using a builtin theme, a
538 name must be supplied to create a custom theme.
539
540 Attributes accessed:
541 highlight_target
542 frame_color_set
543 is_builtin_theme
544
545 Attributes updated:
546 color
547
548 Methods:
549 get_new_theme_name
550 create_new_theme
551 """
552 target = self.highlight_target.get()
553 prev_color = self.frame_color_set.cget('bg')
554 rgbTuplet, color_string = tkColorChooser.askcolor(
555 parent=self, title='Pick new color for : '+target,
556 initialcolor=prev_color)
557 if color_string and (color_string != prev_color):
558 # User didn't cancel and they chose a new color.
559 if self.is_builtin_theme.get(): # Current theme is a built-in.
560 message = ('Your changes will be saved as a new Custom Theme. '
561 'Enter a name for your new Custom Theme below.')
562 new_theme = self.get_new_theme_name(message)
563 if not new_theme: # User cancelled custom theme creation.
564 return
565 else: # Create new custom theme based on previously active theme.
566 self.create_new_theme(new_theme)
567 self.color.set(color_string)
568 else: # Current theme is user defined.
569 self.color.set(color_string)
570
571 def on_new_color_set(self):
572 "Display sample of new color selection on the dialog."
573 new_color=self.color.get()
574 self.frame_color_set.config(bg=new_color) # Set sample.
575 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
576 sample_element = self.theme_elements[self.highlight_target.get()][0]
577 self.highlight_sample.tag_config(sample_element, **{plane:new_color})
578 theme = self.custom_theme.get()
579 theme_element = sample_element + '-' + plane
580 changes.add_option('highlight', theme, theme_element, new_color)
581
582 def get_new_theme_name(self, message):
583 "Return name of new theme from query popup."
584 used_names = (idleConf.GetSectionList('user', 'highlight') +
585 idleConf.GetSectionList('default', 'highlight'))
586 new_theme = SectionName(
587 self, 'New Custom Theme', message, used_names).result
588 return new_theme
589
590 def save_as_new_theme(self):
591 """Prompt for new theme name and create the theme.
592
593 Methods:
594 get_new_theme_name
595 create_new_theme
596 """
597 new_theme_name = self.get_new_theme_name('New Theme Name:')
598 if new_theme_name:
599 self.create_new_theme(new_theme_name)
600
601 def create_new_theme(self, new_theme_name):
602 """Create a new custom theme with the given name.
603
604 Create the new theme based on the previously active theme
605 with the current changes applied. Once it is saved, then
606 activate the new theme.
607
608 Attributes accessed:
609 builtin_theme
610 custom_theme
611
612 Attributes updated:
613 opt_menu_theme_custom
614 is_builtin_theme
615
616 Method:
617 save_new_theme
618 set_theme_type
619 """
620 if self.is_builtin_theme.get():
621 theme_type = 'default'
622 theme_name = self.builtin_theme.get()
623 else:
624 theme_type = 'user'
625 theme_name = self.custom_theme.get()
626 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
627 # Apply any of the old theme's unsaved changes to the new theme.
628 if theme_name in changes['highlight']:
629 theme_changes = changes['highlight'][theme_name]
630 for element in theme_changes:
631 new_theme[element] = theme_changes[element]
632 # Save the new theme.
633 self.save_new_theme(new_theme_name, new_theme)
634 # Change GUI over to the new theme.
635 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
636 custom_theme_list.sort()
637 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
638 self.is_builtin_theme.set(0)
639 self.set_theme_type()
640
641 def set_highlight_target(self):
642 """Set fg/bg toggle and color based on highlight tag target.
643
644 Instance variables accessed:
645 highlight_target
646
647 Attributes updated:
648 radio_fg
649 radio_bg
650 fg_bg_toggle
651
652 Methods:
653 set_color_sample
654
655 Called from:
656 var_changed_highlight_target
657 load_theme_cfg
658 """
659 if self.highlight_target.get() == 'Cursor': # bg not possible
660 self.radio_fg['state'] = DISABLED
661 self.radio_bg['state'] = DISABLED
662 self.fg_bg_toggle.set(1)
663 else: # Both fg and bg can be set.
664 self.radio_fg['state'] = NORMAL
665 self.radio_bg['state'] = NORMAL
666 self.fg_bg_toggle.set(1)
667 self.set_color_sample()
668
669 def set_color_sample_binding(self, *args):
670 """Change color sample based on foreground/background toggle.
671
672 Methods:
673 set_color_sample
674 """
675 self.set_color_sample()
676
677 def set_color_sample(self):
678 """Set the color of the frame background to reflect the selected target.
679
680 Instance variables accessed:
681 theme_elements
682 highlight_target
683 fg_bg_toggle
684 highlight_sample
685
686 Attributes updated:
687 frame_color_set
688 """
689 # Set the color sample area.
690 tag = self.theme_elements[self.highlight_target.get()][0]
691 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
692 color = self.highlight_sample.tag_cget(tag, plane)
693 self.frame_color_set.config(bg=color)
694
695 def paint_theme_sample(self):
696 """Apply the theme colors to each element tag in the sample text.
697
698 Instance attributes accessed:
699 theme_elements
700 is_builtin_theme
701 builtin_theme
702 custom_theme
703
704 Attributes updated:
705 highlight_sample: Set the tag elements to the theme.
706
707 Methods:
708 set_color_sample
709
710 Called from:
711 var_changed_builtin_theme
712 var_changed_custom_theme
713 load_theme_cfg
714 """
715 if self.is_builtin_theme.get(): # Default theme
716 theme = self.builtin_theme.get()
717 else: # User theme
718 theme = self.custom_theme.get()
719 for element_title in self.theme_elements:
720 element = self.theme_elements[element_title][0]
721 colors = idleConf.GetHighlight(theme, element)
722 if element == 'cursor': # Cursor sample needs special painting.
723 colors['background'] = idleConf.GetHighlight(
724 theme, 'normal', fgBg='bg')
725 # Handle any unsaved changes to this theme.
726 if theme in changes['highlight']:
727 theme_dict = changes['highlight'][theme]
728 if element + '-foreground' in theme_dict:
729 colors['foreground'] = theme_dict[element + '-foreground']
730 if element + '-background' in theme_dict:
731 colors['background'] = theme_dict[element + '-background']
732 self.highlight_sample.tag_config(element, **colors)
733 self.set_color_sample()
734
735 def save_new_theme(self, theme_name, theme):
736 """Save a newly created theme to idleConf.
737
738 theme_name - string, the name of the new theme
739 theme - dictionary containing the new theme
740 """
741 if not idleConf.userCfg['highlight'].has_section(theme_name):
742 idleConf.userCfg['highlight'].add_section(theme_name)
743 for element in theme:
744 value = theme[element]
745 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
746
747 def delete_custom_theme(self):
748 """Handle event to delete custom theme.
749
750 The current theme is deactivated and the default theme is
751 activated. The custom theme is permanently removed from
752 the config file.
753
754 Attributes accessed:
755 custom_theme
756
757 Attributes updated:
758 radio_theme_custom
759 opt_menu_theme_custom
760 is_builtin_theme
761 builtin_theme
762
763 Methods:
764 deactivate_current_config
765 save_all_changed_extensions
766 activate_config_changes
767 set_theme_type
768 """
769 theme_name = self.custom_theme.get()
770 delmsg = 'Are you sure you wish to delete the theme %r ?'
771 if not tkMessageBox.askyesno(
772 'Delete Theme', delmsg % theme_name, parent=self):
773 return
774 self.deactivate_current_config()
775 # Remove theme from changes, config, and file.
776 changes.delete_section('highlight', theme_name)
777 # Reload user theme list.
778 item_list = idleConf.GetSectionList('user', 'highlight')
779 item_list.sort()
780 if not item_list:
781 self.radio_theme_custom['state'] = DISABLED
782 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
783 else:
784 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
785 # Revert to default theme.
786 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
787 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
788 # User can't back out of these changes, they must be applied now.
789 changes.save_all()
790 self.save_all_changed_extensions()
791 self.activate_config_changes()
792 self.set_theme_type()
793
794
terryjreedy938e7382017-06-26 20:48:39 -0400795 def create_page_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400796 """Return frame of widgets for Keys tab.
797
Terry Jan Reedya3145902017-08-14 21:45:02 -0400798 Enable users to provisionally change both individual and sets of
799 keybindings (shortcut keys). Except for features implemented as
800 extensions, keybindings are stored in complete sets called
801 keysets. Built-in keysets in idlelib/config-keys.def are fixed
802 as far as the dialog is concerned. Any keyset can be used as the
803 base for a new custom keyset, stored in .idlerc/config-keys.cfg.
804
805 Function load_key_cfg() initializes tk variables and keyset
806 lists and calls load_keys_list for the current keyset.
807 Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
808 keyset_source, which controls if the current set of keybindings
809 are from a builtin or custom keyset. DynOptionMenus builtinlist
810 and customlist contain lists of the builtin and custom keysets,
811 respectively, and the current item from each list is stored in
812 vars builtin_name and custom_name.
813
814 Button delete_custom_keys invokes delete_custom_keys() to delete
815 a custom keyset from idleConf.userCfg['keys'] and changes. Button
816 save_custom_keys invokes save_as_new_key_set() which calls
817 get_new_keys_name() and create_new_key_set() to save a custom keyset
818 and its keybindings to idleConf.userCfg['keys'].
819
820 Listbox bindingslist contains all of the keybindings for the
821 selected keyset. The keybindings are loaded in load_keys_list()
822 and are pairs of (event, [keys]) where keys can be a list
823 of one or more key combinations to bind to the same event.
824 Mouse button 1 click invokes on_bindingslist_select(), which
825 allows button_new_keys to be clicked.
826
827 So, an item is selected in listbindings, which activates
828 button_new_keys, and clicking button_new_keys calls function
829 get_new_keys(). Function get_new_keys() gets the key mappings from the
830 current keyset for the binding event item that was selected. The
831 function then displays another dialog, GetKeysDialog, with the
832 selected binding event and current keys and always new key sequences
833 to be entered for that binding event. If the keys aren't
834 changed, nothing happens. If the keys are changed and the keyset
835 is a builtin, function get_new_keys_name() will be called
836 for input of a custom keyset name. If no name is given, then the
837 change to the keybinding will abort and no updates will be made. If
838 a custom name is entered in the prompt or if the current keyset was
839 already custom (and thus didn't require a prompt), then
840 idleConf.userCfg['keys'] is updated in function create_new_key_set()
841 with the change to the event binding. The item listing in bindingslist
842 is updated with the new keys. Var keybinding is also set which invokes
843 the callback function, var_changed_keybinding, to add the change to
844 the 'keys' or 'extensions' changes tracker based on the binding type.
845
terryjreedy9a09c662017-07-13 23:53:30 -0400846 Tk Variables:
terryjreedye5bb1122017-07-05 00:54:55 -0400847 keybinding: Action/key bindings.
terryjreedy9a09c662017-07-13 23:53:30 -0400848
849 Methods:
terryjreedy9a09c662017-07-13 23:53:30 -0400850 load_keys_list: Reload active set.
terryjreedy9a09c662017-07-13 23:53:30 -0400851 create_new_key_set: Combine active keyset and changes.
Terry Jan Reedya3145902017-08-14 21:45:02 -0400852 set_keys_type: Command for keyset_source.
terryjreedy9a09c662017-07-13 23:53:30 -0400853 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
854 deactivate_current_config: Remove keys bindings in editors.
855
Terry Jan Reedya3145902017-08-14 21:45:02 -0400856 Widgets for keys page frame: (*) widgets bound to self
857 frame_key_sets: LabelFrame
858 frames[0]: Frame
859 (*)builtin_keyset_on: Radiobutton - var keyset_source
860 (*)custom_keyset_on: Radiobutton - var keyset_source
861 (*)builtinlist: DynOptionMenu - var builtin_name,
862 func keybinding_selected
863 (*)customlist: DynOptionMenu - var custom_name,
864 func keybinding_selected
865 (*)keys_message: Label
866 frames[1]: Frame
867 (*)button_delete_custom_keys: Button - delete_custom_keys
868 (*)button_save_custom_keys: Button - save_as_new_key_set
869 frame_custom: LabelFrame
870 frame_target: Frame
871 target_title: Label
872 scroll_target_y: Scrollbar
873 scroll_target_x: Scrollbar
874 (*)bindingslist: ListBox - on_bindingslist_select
875 (*)button_new_keys: Button - get_new_keys & ..._name
terryjreedye5bb1122017-07-05 00:54:55 -0400876 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400877 parent = self.parent
Terry Jan Reedya3145902017-08-14 21:45:02 -0400878 self.builtin_name = tracers.add(
879 StringVar(parent), self.var_changed_builtin_name)
880 self.custom_name = tracers.add(
881 StringVar(parent), self.var_changed_custom_name)
882 self.keyset_source = tracers.add(
883 BooleanVar(parent), self.var_changed_keyset_source)
Terry Jan Reedy02f88d22017-07-28 15:42:43 -0400884 self.keybinding = tracers.add(
885 StringVar(parent), self.var_changed_keybinding)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400886
Terry Jan Reedy8364fef2017-07-29 01:28:05 -0400887 # Widget creation:
888 # body and section frames.
889 frame = Frame(self.note)
terryjreedy938e7382017-06-26 20:48:39 -0400890 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400891 frame, borderwidth=2, relief=GROOVE,
892 text=' Custom Key Bindings ')
terryjreedy938e7382017-06-26 20:48:39 -0400893 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400894 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
terryjreedy938e7382017-06-26 20:48:39 -0400895 #frame_custom
896 frame_target = Frame(frame_custom)
897 target_title = Label(frame_target, text='Action - Key(s)')
898 scroll_target_y = Scrollbar(frame_target)
899 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
Terry Jan Reedya3145902017-08-14 21:45:02 -0400900 self.bindingslist = Listbox(
terryjreedy938e7382017-06-26 20:48:39 -0400901 frame_target, takefocus=FALSE, exportselection=FALSE)
Terry Jan Reedya3145902017-08-14 21:45:02 -0400902 self.bindingslist.bind('<ButtonRelease-1>',
903 self.on_bindingslist_select)
904 scroll_target_y['command'] = self.bindingslist.yview
905 scroll_target_x['command'] = self.bindingslist.xview
906 self.bindingslist['yscrollcommand'] = scroll_target_y.set
907 self.bindingslist['xscrollcommand'] = scroll_target_x.set
terryjreedy938e7382017-06-26 20:48:39 -0400908 self.button_new_keys = Button(
909 frame_custom, text='Get New Keys for Selection',
910 command=self.get_new_keys, state=DISABLED)
911 #frame_key_sets
912 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +0000913 for i in range(2)]
Terry Jan Reedya3145902017-08-14 21:45:02 -0400914 self.builtin_keyset_on = Radiobutton(
915 frames[0], variable=self.keyset_source, value=1,
terryjreedy938e7382017-06-26 20:48:39 -0400916 command=self.set_keys_type, text='Use a Built-in Key Set')
Terry Jan Reedya3145902017-08-14 21:45:02 -0400917 self.custom_keyset_on = Radiobutton(
918 frames[0], variable=self.keyset_source, value=0,
terryjreedy938e7382017-06-26 20:48:39 -0400919 command=self.set_keys_type, text='Use a Custom Key Set')
Terry Jan Reedya3145902017-08-14 21:45:02 -0400920 self.builtinlist = DynOptionMenu(
921 frames[0], self.builtin_name, None, command=None)
922 self.customlist = DynOptionMenu(
923 frames[0], self.custom_name, None, command=None)
terryjreedy938e7382017-06-26 20:48:39 -0400924 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400925 frames[1], text='Delete Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400926 command=self.delete_custom_keys)
Terry Jan Reedya3145902017-08-14 21:45:02 -0400927 self.button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400928 frames[1], text='Save as New Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400929 command=self.save_as_new_key_set)
Terry Jan Reedya3145902017-08-14 21:45:02 -0400930 self.keys_message = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400931
Steven M. Gava60fc7072001-08-04 13:58:22 +0000932 ##widget packing
933 #body
terryjreedy938e7382017-06-26 20:48:39 -0400934 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
935 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
936 #frame_custom
937 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
938 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +0000939 #frame target
terryjreedy938e7382017-06-26 20:48:39 -0400940 frame_target.columnconfigure(0, weight=1)
941 frame_target.rowconfigure(1, weight=1)
942 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
Terry Jan Reedya3145902017-08-14 21:45:02 -0400943 self.bindingslist.grid(row=1, column=0, sticky=NSEW)
terryjreedy938e7382017-06-26 20:48:39 -0400944 scroll_target_y.grid(row=1, column=1, sticky=NS)
945 scroll_target_x.grid(row=2, column=0, sticky=EW)
946 #frame_key_sets
Terry Jan Reedya3145902017-08-14 21:45:02 -0400947 self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
948 self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
949 self.builtinlist.grid(row=0, column=1, sticky=NSEW)
950 self.customlist.grid(row=1, column=1, sticky=NSEW)
951 self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
terryjreedy938e7382017-06-26 20:48:39 -0400952 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Terry Jan Reedya3145902017-08-14 21:45:02 -0400953 self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +0000954 frames[0].pack(side=TOP, fill=BOTH, expand=True)
955 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000956 return frame
957
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400958 def load_key_cfg(self):
959 "Load current configuration settings for the keybinding options."
960 # Set current keys type radiobutton.
Terry Jan Reedya3145902017-08-14 21:45:02 -0400961 self.keyset_source.set(idleConf.GetOption(
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400962 'main', 'Keys', 'default', type='bool', default=1))
963 # Set current keys.
964 current_option = idleConf.CurrentKeys()
965 # Load available keyset option menus.
Terry Jan Reedya3145902017-08-14 21:45:02 -0400966 if self.keyset_source.get(): # Default theme selected.
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400967 item_list = idleConf.GetSectionList('default', 'keys')
968 item_list.sort()
Terry Jan Reedya3145902017-08-14 21:45:02 -0400969 self.builtinlist.SetMenu(item_list, current_option)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400970 item_list = idleConf.GetSectionList('user', 'keys')
971 item_list.sort()
972 if not item_list:
Terry Jan Reedya3145902017-08-14 21:45:02 -0400973 self.custom_keyset_on['state'] = DISABLED
974 self.custom_name.set('- no custom keys -')
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400975 else:
Terry Jan Reedya3145902017-08-14 21:45:02 -0400976 self.customlist.SetMenu(item_list, item_list[0])
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400977 else: # User key set selected.
978 item_list = idleConf.GetSectionList('user', 'keys')
979 item_list.sort()
Terry Jan Reedya3145902017-08-14 21:45:02 -0400980 self.customlist.SetMenu(item_list, current_option)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400981 item_list = idleConf.GetSectionList('default', 'keys')
982 item_list.sort()
Terry Jan Reedya3145902017-08-14 21:45:02 -0400983 self.builtinlist.SetMenu(item_list, idleConf.default_keys())
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400984 self.set_keys_type()
985 # Load keyset element list.
986 keyset_name = idleConf.CurrentKeys()
987 self.load_keys_list(keyset_name)
988
Terry Jan Reedya3145902017-08-14 21:45:02 -0400989 def var_changed_builtin_name(self, *params):
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400990 "Process selection of builtin key set."
991 old_keys = (
992 'IDLE Classic Windows',
993 'IDLE Classic Unix',
994 'IDLE Classic Mac',
995 'IDLE Classic OSX',
996 )
Terry Jan Reedya3145902017-08-14 21:45:02 -0400997 value = self.builtin_name.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -0400998 if value not in old_keys:
999 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
1000 changes.add_option('main', 'Keys', 'name', old_keys[0])
1001 changes.add_option('main', 'Keys', 'name2', value)
Terry Jan Reedya3145902017-08-14 21:45:02 -04001002 self.keys_message['text'] = 'New key set, see Help'
1003 self.keys_message['fg'] = '#500000'
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001004 else:
1005 changes.add_option('main', 'Keys', 'name', value)
1006 changes.add_option('main', 'Keys', 'name2', '')
Terry Jan Reedya3145902017-08-14 21:45:02 -04001007 self.keys_message['text'] = ''
1008 self.keys_message['fg'] = 'black'
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001009 self.load_keys_list(value)
1010
Terry Jan Reedya3145902017-08-14 21:45:02 -04001011 def var_changed_custom_name(self, *params):
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001012 "Process selection of custom key set."
Terry Jan Reedya3145902017-08-14 21:45:02 -04001013 value = self.custom_name.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001014 if value != '- no custom keys -':
1015 changes.add_option('main', 'Keys', 'name', value)
1016 self.load_keys_list(value)
1017
Terry Jan Reedya3145902017-08-14 21:45:02 -04001018 def var_changed_keyset_source(self, *params):
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001019 "Process toggle between builtin key set and custom key set."
Terry Jan Reedya3145902017-08-14 21:45:02 -04001020 value = self.keyset_source.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001021 changes.add_option('main', 'Keys', 'default', value)
1022 if value:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001023 self.var_changed_builtin_name()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001024 else:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001025 self.var_changed_custom_name()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001026
1027 def var_changed_keybinding(self, *params):
1028 "Store change to a keybinding."
1029 value = self.keybinding.get()
Terry Jan Reedya3145902017-08-14 21:45:02 -04001030 key_set = self.custom_name.get()
1031 event = self.bindingslist.get(ANCHOR).split()[0]
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001032 if idleConf.IsCoreBinding(event):
1033 changes.add_option('keys', key_set, event, value)
1034 else: # Event is an extension binding.
1035 ext_name = idleConf.GetExtnNameForEvent(event)
1036 ext_keybind_section = ext_name + '_cfgBindings'
1037 changes.add_option('extensions', ext_keybind_section, event, value)
1038
1039 def set_keys_type(self):
1040 "Set available screen options based on builtin or custom key set."
Terry Jan Reedya3145902017-08-14 21:45:02 -04001041 if self.keyset_source.get():
1042 self.builtinlist['state'] = NORMAL
1043 self.customlist['state'] = DISABLED
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001044 self.button_delete_custom_keys['state'] = DISABLED
1045 else:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001046 self.builtinlist['state'] = DISABLED
1047 self.custom_keyset_on['state'] = NORMAL
1048 self.customlist['state'] = NORMAL
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001049 self.button_delete_custom_keys['state'] = NORMAL
1050
1051 def get_new_keys(self):
1052 """Handle event to change key binding for selected line.
1053
1054 A selection of a key/binding in the list of current
1055 bindings pops up a dialog to enter a new binding. If
1056 the current key set is builtin and a binding has
1057 changed, then a name for a custom key set needs to be
1058 entered for the change to be applied.
1059 """
Terry Jan Reedya3145902017-08-14 21:45:02 -04001060 list_index = self.bindingslist.index(ANCHOR)
1061 binding = self.bindingslist.get(list_index)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001062 bind_name = binding.split()[0]
Terry Jan Reedya3145902017-08-14 21:45:02 -04001063 if self.keyset_source.get():
1064 current_key_set_name = self.builtin_name.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001065 else:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001066 current_key_set_name = self.custom_name.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001067 current_bindings = idleConf.GetCurrentKeySet()
1068 if current_key_set_name in changes['keys']: # unsaved changes
1069 key_set_changes = changes['keys'][current_key_set_name]
1070 for event in key_set_changes:
1071 current_bindings[event] = key_set_changes[event].split()
1072 current_key_sequences = list(current_bindings.values())
1073 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1074 current_key_sequences).result
1075 if new_keys:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001076 if self.keyset_source.get(): # Current key set is a built-in.
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001077 message = ('Your changes will be saved as a new Custom Key Set.'
1078 ' Enter a name for your new Custom Key Set below.')
1079 new_keyset = self.get_new_keys_name(message)
1080 if not new_keyset: # User cancelled custom key set creation.
Terry Jan Reedya3145902017-08-14 21:45:02 -04001081 self.bindingslist.select_set(list_index)
1082 self.bindingslist.select_anchor(list_index)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001083 return
1084 else: # Create new custom key set based on previously active key set.
1085 self.create_new_key_set(new_keyset)
Terry Jan Reedya3145902017-08-14 21:45:02 -04001086 self.bindingslist.delete(list_index)
1087 self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
1088 self.bindingslist.select_set(list_index)
1089 self.bindingslist.select_anchor(list_index)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001090 self.keybinding.set(new_keys)
1091 else:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001092 self.bindingslist.select_set(list_index)
1093 self.bindingslist.select_anchor(list_index)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001094
1095 def get_new_keys_name(self, message):
1096 "Return new key set name from query popup."
1097 used_names = (idleConf.GetSectionList('user', 'keys') +
1098 idleConf.GetSectionList('default', 'keys'))
1099 new_keyset = SectionName(
1100 self, 'New Custom Key Set', message, used_names).result
1101 return new_keyset
1102
1103 def save_as_new_key_set(self):
1104 "Prompt for name of new key set and save changes using that name."
1105 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1106 if new_keys_name:
1107 self.create_new_key_set(new_keys_name)
1108
Terry Jan Reedya3145902017-08-14 21:45:02 -04001109 def on_bindingslist_select(self, event):
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001110 "Activate button to assign new keys to selected action."
1111 self.button_new_keys['state'] = NORMAL
1112
1113 def create_new_key_set(self, new_key_set_name):
1114 """Create a new custom key set with the given name.
1115
Terry Jan Reedya3145902017-08-14 21:45:02 -04001116 Copy the bindings/keys from the previously active keyset
1117 to the new keyset and activate the new custom keyset.
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001118 """
Terry Jan Reedya3145902017-08-14 21:45:02 -04001119 if self.keyset_source.get():
1120 prev_key_set_name = self.builtin_name.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001121 else:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001122 prev_key_set_name = self.custom_name.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001123 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1124 new_keys = {}
1125 for event in prev_keys: # Add key set to changed items.
1126 event_name = event[2:-2] # Trim off the angle brackets.
1127 binding = ' '.join(prev_keys[event])
1128 new_keys[event_name] = binding
1129 # Handle any unsaved changes to prev key set.
1130 if prev_key_set_name in changes['keys']:
1131 key_set_changes = changes['keys'][prev_key_set_name]
1132 for event in key_set_changes:
1133 new_keys[event] = key_set_changes[event]
1134 # Save the new key set.
1135 self.save_new_key_set(new_key_set_name, new_keys)
1136 # Change GUI over to the new key set.
1137 custom_key_list = idleConf.GetSectionList('user', 'keys')
1138 custom_key_list.sort()
Terry Jan Reedya3145902017-08-14 21:45:02 -04001139 self.customlist.SetMenu(custom_key_list, new_key_set_name)
1140 self.keyset_source.set(0)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001141 self.set_keys_type()
1142
1143 def load_keys_list(self, keyset_name):
1144 """Reload the list of action/key binding pairs for the active key set.
1145
1146 An action/key binding can be selected to change the key binding.
1147 """
Terry Jan Reedya3145902017-08-14 21:45:02 -04001148 reselect = False
1149 if self.bindingslist.curselection():
1150 reselect = True
1151 list_index = self.bindingslist.index(ANCHOR)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001152 keyset = idleConf.GetKeySet(keyset_name)
1153 bind_names = list(keyset.keys())
1154 bind_names.sort()
Terry Jan Reedya3145902017-08-14 21:45:02 -04001155 self.bindingslist.delete(0, END)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001156 for bind_name in bind_names:
1157 key = ' '.join(keyset[bind_name])
1158 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1159 if keyset_name in changes['keys']:
1160 # Handle any unsaved changes to this key set.
1161 if bind_name in changes['keys'][keyset_name]:
1162 key = changes['keys'][keyset_name][bind_name]
Terry Jan Reedya3145902017-08-14 21:45:02 -04001163 self.bindingslist.insert(END, bind_name+' - '+key)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001164 if reselect:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001165 self.bindingslist.see(list_index)
1166 self.bindingslist.select_set(list_index)
1167 self.bindingslist.select_anchor(list_index)
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001168
1169 def save_new_key_set(self, keyset_name, keyset):
1170 """Save a newly created core key set.
1171
Terry Jan Reedya3145902017-08-14 21:45:02 -04001172 Add keyset to idleConf.userCfg['keys'], not to disk.
1173 If the keyset doesn't exist, it is created. The
1174 binding/keys are taken from the keyset argument.
1175
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001176 keyset_name - string, the name of the new key set
Terry Jan Reedya3145902017-08-14 21:45:02 -04001177 keyset - dictionary containing the new keybindings
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001178 """
1179 if not idleConf.userCfg['keys'].has_section(keyset_name):
1180 idleConf.userCfg['keys'].add_section(keyset_name)
1181 for event in keyset:
1182 value = keyset[event]
1183 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1184
1185 def delete_custom_keys(self):
1186 """Handle event to delete a custom key set.
1187
1188 Applying the delete deactivates the current configuration and
1189 reverts to the default. The custom key set is permanently
1190 deleted from the config file.
1191 """
Terry Jan Reedya3145902017-08-14 21:45:02 -04001192 keyset_name=self.custom_name.get()
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001193 delmsg = 'Are you sure you wish to delete the key set %r ?'
1194 if not tkMessageBox.askyesno(
1195 'Delete Key Set', delmsg % keyset_name, parent=self):
1196 return
1197 self.deactivate_current_config()
1198 # Remove key set from changes, config, and file.
1199 changes.delete_section('keys', keyset_name)
1200 # Reload user key set list.
1201 item_list = idleConf.GetSectionList('user', 'keys')
1202 item_list.sort()
1203 if not item_list:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001204 self.custom_keyset_on['state'] = DISABLED
1205 self.customlist.SetMenu(item_list, '- no custom keys -')
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001206 else:
Terry Jan Reedya3145902017-08-14 21:45:02 -04001207 self.customlist.SetMenu(item_list, item_list[0])
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001208 # Revert to default key set.
Terry Jan Reedya3145902017-08-14 21:45:02 -04001209 self.keyset_source.set(idleConf.defaultCfg['main']
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001210 .Get('Keys', 'default'))
Terry Jan Reedya3145902017-08-14 21:45:02 -04001211 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Terry Jan Reedy2cbb6732017-07-27 20:50:39 -04001212 or idleConf.default_keys())
1213 # User can't back out of these changes, they must be applied now.
1214 changes.save_all()
1215 self.save_all_changed_extensions()
1216 self.activate_config_changes()
1217 self.set_keys_type()
1218
1219 def deactivate_current_config(self):
1220 """Remove current key bindings.
1221
1222 Iterate over window instances defined in parent and remove
1223 the keybindings.
1224 """
1225 # Before a config is saved, some cleanup of current
1226 # config must be done - remove the previous keybindings.
1227 win_instances = self.parent.instance_dict.keys()
1228 for instance in win_instances:
1229 instance.RemoveKeybindings()
1230
1231 def activate_config_changes(self):
1232 """Apply configuration changes to current windows.
1233
1234 Dynamically update the current parent window instances
1235 with some of the configuration changes.
1236 """
1237 win_instances = self.parent.instance_dict.keys()
1238 for instance in win_instances:
1239 instance.ResetColorizer()
1240 instance.ResetFont()
1241 instance.set_notabs_indentwidth()
1242 instance.ApplyKeybindings()
1243 instance.reset_help_menu_entries()
1244
terryjreedy938e7382017-06-26 20:48:39 -04001245 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001246 """Part of the config dialog used for configuring IDLE extensions.
1247
1248 This code is generic - it works for any and all IDLE extensions.
1249
1250 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001251 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001252 GUI interface to change the configuration values, and saves the
1253 changes using idleConf.
1254
1255 Not all changes take effect immediately - some may require restarting IDLE.
1256 This depends on each extension's implementation.
1257
1258 All values are treated as text, and it is up to the user to supply
1259 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001260 which are boolean, and can be toggled with a True/False button.
terryjreedy9a09c662017-07-13 23:53:30 -04001261
1262 Methods:
1263 load_extentions:
1264 extension_selected: Handle selection from list.
1265 create_extension_frame: Hold widgets for one extension.
1266 set_extension_value: Set in userCfg['extensions'].
1267 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001268 """
1269 parent = self.parent
Terry Jan Reedy8364fef2017-07-29 01:28:05 -04001270 frame = Frame(self.note)
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001271 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1272 self.ext_userCfg = idleConf.userCfg['extensions']
1273 self.is_int = self.register(is_int)
1274 self.load_extensions()
terryjreedye5bb1122017-07-05 00:54:55 -04001275 # Create widgets - a listbox shows all available extensions, with the
1276 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001277 self.extension_names = StringVar(self)
1278 frame.rowconfigure(0, weight=1)
1279 frame.columnconfigure(2, weight=1)
1280 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1281 selectmode='browse')
1282 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1283 scroll = Scrollbar(frame, command=self.extension_list.yview)
1284 self.extension_list.yscrollcommand=scroll.set
1285 self.details_frame = LabelFrame(frame, width=250, height=250)
1286 self.extension_list.grid(column=0, row=0, sticky='nws')
1287 scroll.grid(column=1, row=0, sticky='ns')
1288 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1289 frame.configure(padx=10, pady=10)
1290 self.config_frame = {}
1291 self.current_extension = None
1292
1293 self.outerframe = self # TEMPORARY
1294 self.tabbed_page_set = self.extension_list # TEMPORARY
1295
terryjreedye5bb1122017-07-05 00:54:55 -04001296 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001297 ext_names = ''
1298 for ext_name in sorted(self.extensions):
1299 self.create_extension_frame(ext_name)
1300 ext_names = ext_names + '{' + ext_name + '} '
1301 self.extension_names.set(ext_names)
1302 self.extension_list.selection_set(0)
1303 self.extension_selected(None)
1304
Terry Jan Reedy8364fef2017-07-29 01:28:05 -04001305 return frame
1306
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001307 def load_extensions(self):
1308 "Fill self.extensions with data from the default and user configs."
1309 self.extensions = {}
1310 for ext_name in idleConf.GetExtensions(active_only=False):
1311 self.extensions[ext_name] = []
1312
1313 for ext_name in self.extensions:
1314 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1315
terryjreedye5bb1122017-07-05 00:54:55 -04001316 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001317 enables = [opt_name for opt_name in opt_list
1318 if opt_name.startswith('enable')]
1319 for opt_name in enables:
1320 opt_list.remove(opt_name)
1321 opt_list = enables + opt_list
1322
1323 for opt_name in opt_list:
1324 def_str = self.ext_defaultCfg.Get(
1325 ext_name, opt_name, raw=True)
1326 try:
1327 def_obj = {'True':True, 'False':False}[def_str]
1328 opt_type = 'bool'
1329 except KeyError:
1330 try:
1331 def_obj = int(def_str)
1332 opt_type = 'int'
1333 except ValueError:
1334 def_obj = def_str
1335 opt_type = None
1336 try:
1337 value = self.ext_userCfg.Get(
1338 ext_name, opt_name, type=opt_type, raw=True,
1339 default=def_obj)
terryjreedye5bb1122017-07-05 00:54:55 -04001340 except ValueError: # Need this until .Get fixed.
1341 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001342 var = StringVar(self)
1343 var.set(str(value))
1344
1345 self.extensions[ext_name].append({'name': opt_name,
1346 'type': opt_type,
1347 'default': def_str,
1348 'value': value,
1349 'var': var,
1350 })
1351
1352 def extension_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001353 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001354 newsel = self.extension_list.curselection()
1355 if newsel:
1356 newsel = self.extension_list.get(newsel)
1357 if newsel is None or newsel != self.current_extension:
1358 if self.current_extension:
1359 self.details_frame.config(text='')
1360 self.config_frame[self.current_extension].grid_forget()
1361 self.current_extension = None
1362 if newsel:
1363 self.details_frame.config(text=newsel)
1364 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1365 self.current_extension = newsel
1366
1367 def create_extension_frame(self, ext_name):
1368 """Create a frame holding the widgets to configure one extension"""
1369 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1370 self.config_frame[ext_name] = f
1371 entry_area = f.interior
terryjreedye5bb1122017-07-05 00:54:55 -04001372 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001373 for row, opt in enumerate(self.extensions[ext_name]):
terryjreedye5bb1122017-07-05 00:54:55 -04001374 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001375 label = Label(entry_area, text=opt['name'])
1376 label.grid(row=row, column=0, sticky=NW)
1377 var = opt['var']
1378 if opt['type'] == 'bool':
1379 Checkbutton(entry_area, textvariable=var, variable=var,
1380 onvalue='True', offvalue='False',
1381 indicatoron=FALSE, selectcolor='', width=8
1382 ).grid(row=row, column=1, sticky=W, padx=7)
1383 elif opt['type'] == 'int':
1384 Entry(entry_area, textvariable=var, validate='key',
1385 validatecommand=(self.is_int, '%P')
1386 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1387
1388 else:
1389 Entry(entry_area, textvariable=var
1390 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1391 return
1392
1393 def set_extension_value(self, section, opt):
terryjreedye5bb1122017-07-05 00:54:55 -04001394 """Return True if the configuration was added or changed.
1395
1396 If the value is the same as the default, then remove it
1397 from user config file.
1398 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001399 name = opt['name']
1400 default = opt['default']
1401 value = opt['var'].get().strip() or default
1402 opt['var'].set(value)
1403 # if self.defaultCfg.has_section(section):
terryjreedye5bb1122017-07-05 00:54:55 -04001404 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001405 if (value == default):
1406 return self.ext_userCfg.RemoveOption(section, name)
terryjreedye5bb1122017-07-05 00:54:55 -04001407 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001408 return self.ext_userCfg.SetOption(section, name, value)
1409
1410 def save_all_changed_extensions(self):
terryjreedy9a09c662017-07-13 23:53:30 -04001411 """Save configuration changes to the user config file.
1412
1413 Attributes accessed:
1414 extensions
1415
1416 Methods:
1417 set_extension_value
1418 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001419 has_changes = False
1420 for ext_name in self.extensions:
1421 options = self.extensions[ext_name]
1422 for opt in options:
1423 if self.set_extension_value(ext_name, opt):
1424 has_changes = True
1425 if has_changes:
1426 self.ext_userCfg.Save()
1427
1428
Terry Jan Reedy48fcc722017-08-01 01:00:33 -04001429# class TabPage(Frame): # A template for Page classes.
1430# def __init__(self, master):
1431# super().__init__(master)
1432# self.create_page_tab()
1433# self.load_tab_cfg()
1434# def create_page_tab(self):
1435# # Define tk vars and register var and callback with tracers.
1436# # Create subframes and widgets.
1437# # Pack widgets.
1438# def load_tab_cfg(self):
1439# # Initialize widgets with data from idleConf.
1440# def var_changed_var_name():
1441# # For each tk var that needs other than default callback.
1442# def other_methods():
1443# # Define tab-specific behavior.
1444
1445
Terry Jan Reedy75822262017-07-30 15:00:50 -04001446class FontPage(Frame):
1447
Terry Jan Reedy48fcc722017-08-01 01:00:33 -04001448 def __init__(self, master, highpage):
1449 super().__init__(master)
Terry Jan Reedy75822262017-07-30 15:00:50 -04001450 self.highlight_sample = highpage.highlight_sample
1451 self.create_page_font_tab()
1452 self.load_font_cfg()
1453 self.load_tab_cfg()
1454
1455 def create_page_font_tab(self):
1456 """Return frame of widgets for Font/Tabs tab.
1457
1458 Fonts: Enable users to provisionally change font face, size, or
1459 boldness and to see the consequence of proposed choices. Each
1460 action set 3 options in changes structuree and changes the
1461 corresponding aspect of the font sample on this page and
1462 highlight sample on highlight page.
1463
1464 Function load_font_cfg initializes font vars and widgets from
1465 idleConf entries and tk.
1466
1467 Fontlist: mouse button 1 click or up or down key invoke
1468 on_fontlist_select(), which sets var font_name.
1469
1470 Sizelist: clicking the menubutton opens the dropdown menu. A
1471 mouse button 1 click or return key sets var font_size.
1472
1473 Bold_toggle: clicking the box toggles var font_bold.
1474
1475 Changing any of the font vars invokes var_changed_font, which
1476 adds all 3 font options to changes and calls set_samples.
1477 Set_samples applies a new font constructed from the font vars to
1478 font_sample and to highlight_sample on the hightlight page.
1479
1480 Tabs: Enable users to change spaces entered for indent tabs.
1481 Changing indent_scale value with the mouse sets Var space_num,
1482 which invokes the default callback to add an entry to
1483 changes. Load_tab_cfg initializes space_num to default.
1484
Terry Jan Reedya3145902017-08-14 21:45:02 -04001485 Widgets for FontPage(Frame): (*) widgets bound to self
1486 frame_font: LabelFrame
1487 frame_font_name: Frame
1488 font_name_title: Label
1489 (*)fontlist: ListBox - font_name
1490 scroll_font: Scrollbar
1491 frame_font_param: Frame
1492 font_size_title: Label
1493 (*)sizelist: DynOptionMenu - font_size
1494 (*)bold_toggle: Checkbutton - font_bold
1495 frame_font_sample: Frame
1496 (*)font_sample: Label
1497 frame_indent: LabelFrame
1498 indent_title: Label
1499 (*)indent_scale: Scale - space_num
Terry Jan Reedy75822262017-07-30 15:00:50 -04001500 """
Terry Jan Reedy48fcc722017-08-01 01:00:33 -04001501 self.font_name = tracers.add(StringVar(self), self.var_changed_font)
1502 self.font_size = tracers.add(StringVar(self), self.var_changed_font)
1503 self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
Terry Jan Reedy75822262017-07-30 15:00:50 -04001504 self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
1505
1506 # Create widgets:
1507 # body and body section frames.
Terry Jan Reedy75822262017-07-30 15:00:50 -04001508 frame_font = LabelFrame(
Terry Jan Reedy48fcc722017-08-01 01:00:33 -04001509 self, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
Terry Jan Reedy75822262017-07-30 15:00:50 -04001510 frame_indent = LabelFrame(
Terry Jan Reedy48fcc722017-08-01 01:00:33 -04001511 self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
Terry Jan Reedy75822262017-07-30 15:00:50 -04001512 # frame_font.
1513 frame_font_name = Frame(frame_font)
1514 frame_font_param = Frame(frame_font)
1515 font_name_title = Label(
1516 frame_font_name, justify=LEFT, text='Font Face :')
1517 self.fontlist = Listbox(frame_font_name, height=5,
1518 takefocus=True, exportselection=FALSE)
1519 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
1520 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
1521 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
1522 scroll_font = Scrollbar(frame_font_name)
1523 scroll_font.config(command=self.fontlist.yview)
1524 self.fontlist.config(yscrollcommand=scroll_font.set)
1525 font_size_title = Label(frame_font_param, text='Size :')
1526 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
1527 self.bold_toggle = Checkbutton(
1528 frame_font_param, variable=self.font_bold,
1529 onvalue=1, offvalue=0, text='Bold')
1530 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
Terry Jan Reedy48fcc722017-08-01 01:00:33 -04001531 temp_font = tkFont.Font(self, ('courier', 10, 'normal'))
Terry Jan Reedy75822262017-07-30 15:00:50 -04001532 self.font_sample = Label(
1533 frame_font_sample, justify=LEFT, font=temp_font,
1534 text='AaBbCcDdEe\nFfGgHhIiJj\n1234567890\n#:+=(){}[]')
1535 # frame_indent.
1536 indent_title = Label(
1537 frame_indent, justify=LEFT,
1538 text='Python Standard: 4 Spaces!')
1539 self.indent_scale = Scale(
1540 frame_indent, variable=self.space_num,
1541 orient='horizontal', tickinterval=2, from_=2, to=16)
1542
1543 # Pack widgets:
1544 # body.
1545 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
1546 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
1547 # frame_font.
1548 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
1549 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
1550 font_name_title.pack(side=TOP, anchor=W)
1551 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
1552 scroll_font.pack(side=LEFT, fill=Y)
1553 font_size_title.pack(side=LEFT, anchor=W)
1554 self.sizelist.pack(side=LEFT, anchor=W)
1555 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
1556 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1557 self.font_sample.pack(expand=TRUE, fill=BOTH)
1558 # frame_indent.
1559 frame_indent.pack(side=TOP, fill=X)
1560 indent_title.pack(side=TOP, anchor=W, padx=5)
1561 self.indent_scale.pack(side=TOP, padx=5, fill=X)
1562
Terry Jan Reedy75822262017-07-30 15:00:50 -04001563 def load_font_cfg(self):
1564 """Load current configuration settings for the font options.
1565
1566 Retrieve current font with idleConf.GetFont and font families
1567 from tk. Setup fontlist and set font_name. Setup sizelist,
1568 which sets font_size. Set font_bold. Call set_samples.
1569 """
1570 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
1571 font_name = configured_font[0].lower()
1572 font_size = configured_font[1]
1573 font_bold = configured_font[2]=='bold'
1574
1575 # Set editor font selection list and font_name.
1576 fonts = list(tkFont.families(self))
1577 fonts.sort()
1578 for font in fonts:
1579 self.fontlist.insert(END, font)
1580 self.font_name.set(font_name)
1581 lc_fonts = [s.lower() for s in fonts]
1582 try:
1583 current_font_index = lc_fonts.index(font_name)
1584 self.fontlist.see(current_font_index)
1585 self.fontlist.select_set(current_font_index)
1586 self.fontlist.select_anchor(current_font_index)
1587 self.fontlist.activate(current_font_index)
1588 except ValueError:
1589 pass
1590 # Set font size dropdown.
1591 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
1592 '16', '18', '20', '22', '25', '29', '34', '40'),
1593 font_size)
1594 # Set font weight.
1595 self.font_bold.set(font_bold)
1596 self.set_samples()
1597
1598 def var_changed_font(self, *params):
1599 """Store changes to font attributes.
1600
1601 When one font attribute changes, save them all, as they are
1602 not independent from each other. In particular, when we are
1603 overriding the default font, we need to write out everything.
1604 """
1605 value = self.font_name.get()
1606 changes.add_option('main', 'EditorWindow', 'font', value)
1607 value = self.font_size.get()
1608 changes.add_option('main', 'EditorWindow', 'font-size', value)
1609 value = self.font_bold.get()
1610 changes.add_option('main', 'EditorWindow', 'font-bold', value)
1611 self.set_samples()
1612
1613 def on_fontlist_select(self, event):
1614 """Handle selecting a font from the list.
1615
1616 Event can result from either mouse click or Up or Down key.
1617 Set font_name and example displays to selection.
1618 """
1619 font = self.fontlist.get(
1620 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
1621 self.font_name.set(font.lower())
1622
1623 def set_samples(self, event=None):
1624 """Update update both screen samples with the font settings.
1625
1626 Called on font initialization and change events.
1627 Accesses font_name, font_size, and font_bold Variables.
1628 Updates font_sample and hightlight page highlight_sample.
1629 """
1630 font_name = self.font_name.get()
1631 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
1632 new_font = (font_name, self.font_size.get(), font_weight)
1633 self.font_sample['font'] = new_font
1634 self.highlight_sample['font'] = new_font
1635
1636 def load_tab_cfg(self):
1637 """Load current configuration settings for the tab options.
1638
1639 Attributes updated:
1640 space_num: Set to value from idleConf.
1641 """
1642 # Set indent sizes.
1643 space_num = idleConf.GetOption(
1644 'main', 'Indent', 'num-spaces', default=4, type='int')
1645 self.space_num.set(space_num)
1646
1647 def var_changed_space_num(self, *params):
1648 "Store change to indentation size."
1649 value = self.space_num.get()
1650 changes.add_option('main', 'Indent', 'num-spaces', value)
1651
1652
Terry Jan Reedy8c4e5be2017-07-30 19:02:51 -04001653class GenPage(Frame):
1654
Terry Jan Reedy48fcc722017-08-01 01:00:33 -04001655 def __init__(self, master):
1656 super().__init__(master)
Terry Jan Reedy8c4e5be2017-07-30 19:02:51 -04001657 self.create_page_general()
1658 self.load_general_cfg()
1659
1660 def create_page_general(self):
1661 """Return frame of widgets for General tab.
1662
1663 Enable users to provisionally change general options. Function
1664 load_general_cfg intializes tk variables and helplist using
1665 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1666 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1667 set var autosave. Entry boxes win_width_int and win_height_int
1668 set var win_width and win_height. Setting var_name invokes the
1669 default callback that adds option to changes.
1670
1671 Helplist: load_general_cfg loads list user_helplist with
1672 name, position pairs and copies names to listbox helplist.
1673 Clicking a name invokes help_source selected. Clicking
1674 button_helplist_name invokes helplist_item_name, which also
1675 changes user_helplist. These functions all call
1676 set_add_delete_state. All but load call update_help_changes to
1677 rewrite changes['main']['HelpFiles'].
1678
Terry Jan Reedya3145902017-08-14 21:45:02 -04001679 Widgets for GenPage(Frame): (*) widgets bound to self
1680 frame_run: LabelFrame
1681 startup_title: Label
1682 (*)startup_editor_on: Radiobutton - startup_edit
1683 (*)startup_shell_on: Radiobutton - startup_edit
1684 frame_save: LabelFrame
1685 run_save_title: Label
1686 (*)save_ask_on: Radiobutton - autosave
1687 (*)save_auto_on: Radiobutton - autosave
1688 frame_win_size: LabelFrame
1689 win_size_title: Label
1690 win_width_title: Label
1691 (*)win_width_int: Entry - win_width
1692 win_height_title: Label
1693 (*)win_height_int: Entry - win_height
1694 frame_help: LabelFrame
1695 frame_helplist: Frame
1696 frame_helplist_buttons: Frame
1697 (*)button_helplist_edit
1698 (*)button_helplist_add
1699 (*)button_helplist_remove
1700 (*)helplist: ListBox
1701 scroll_helplist: Scrollbar
Terry Jan Reedy8c4e5be2017-07-30 19:02:51 -04001702 """
1703 self.startup_edit = tracers.add(
1704 IntVar(self), ('main', 'General', 'editor-on-startup'))
1705 self.autosave = tracers.add(
1706 IntVar(self), ('main', 'General', 'autosave'))
1707 self.win_width = tracers.add(
1708 StringVar(self), ('main', 'EditorWindow', 'width'))
1709 self.win_height = tracers.add(
1710 StringVar(self), ('main', 'EditorWindow', 'height'))
1711
1712 # Create widgets:
1713 # Section frames.
1714 frame_run = LabelFrame(self, borderwidth=2, relief=GROOVE,
1715 text=' Startup Preferences ')
1716 frame_save = LabelFrame(self, borderwidth=2, relief=GROOVE,
1717 text=' autosave Preferences ')
1718 frame_win_size = Frame(self, borderwidth=2, relief=GROOVE)
1719 frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
1720 text=' Additional Help Sources ')
1721 # frame_run.
1722 startup_title = Label(frame_run, text='At Startup')
1723 self.startup_editor_on = Radiobutton(
1724 frame_run, variable=self.startup_edit, value=1,
1725 text="Open Edit Window")
1726 self.startup_shell_on = Radiobutton(
1727 frame_run, variable=self.startup_edit, value=0,
1728 text='Open Shell Window')
1729 # frame_save.
1730 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
1731 self.save_ask_on = Radiobutton(
1732 frame_save, variable=self.autosave, value=0,
1733 text="Prompt to Save")
1734 self.save_auto_on = Radiobutton(
1735 frame_save, variable=self.autosave, value=1,
1736 text='No Prompt')
1737 # frame_win_size.
1738 win_size_title = Label(
1739 frame_win_size, text='Initial Window Size (in characters)')
1740 win_width_title = Label(frame_win_size, text='Width')
1741 self.win_width_int = Entry(
1742 frame_win_size, textvariable=self.win_width, width=3)
1743 win_height_title = Label(frame_win_size, text='Height')
1744 self.win_height_int = Entry(
1745 frame_win_size, textvariable=self.win_height, width=3)
1746 # frame_help.
1747 frame_helplist = Frame(frame_help)
1748 frame_helplist_buttons = Frame(frame_helplist)
1749 self.helplist = Listbox(
1750 frame_helplist, height=5, takefocus=True,
1751 exportselection=FALSE)
1752 scroll_helplist = Scrollbar(frame_helplist)
1753 scroll_helplist['command'] = self.helplist.yview
1754 self.helplist['yscrollcommand'] = scroll_helplist.set
1755 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
1756 self.button_helplist_edit = Button(
1757 frame_helplist_buttons, text='Edit', state=DISABLED,
1758 width=8, command=self.helplist_item_edit)
1759 self.button_helplist_add = Button(
1760 frame_helplist_buttons, text='Add',
1761 width=8, command=self.helplist_item_add)
1762 self.button_helplist_remove = Button(
1763 frame_helplist_buttons, text='Remove', state=DISABLED,
1764 width=8, command=self.helplist_item_remove)
1765
1766 # Pack widgets:
1767 # body.
1768 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
1769 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
1770 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
1771 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1772 # frame_run.
1773 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1774 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1775 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1776 # frame_save.
1777 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1778 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1779 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1780 # frame_win_size.
1781 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
1782 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
1783 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
1784 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
1785 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
1786 # frame_help.
1787 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
1788 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1789 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
1790 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
1791 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
1792 self.button_helplist_add.pack(side=TOP, anchor=W)
1793 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
1794
1795 def load_general_cfg(self):
1796 "Load current configuration settings for the general options."
1797 # Set startup state.
1798 self.startup_edit.set(idleConf.GetOption(
1799 'main', 'General', 'editor-on-startup', default=0, type='bool'))
1800 # Set autosave state.
1801 self.autosave.set(idleConf.GetOption(
1802 'main', 'General', 'autosave', default=0, type='bool'))
1803 # Set initial window size.
1804 self.win_width.set(idleConf.GetOption(
1805 'main', 'EditorWindow', 'width', type='int'))
1806 self.win_height.set(idleConf.GetOption(
1807 'main', 'EditorWindow', 'height', type='int'))
1808 # Set additional help sources.
1809 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1810 self.helplist.delete(0, 'end')
1811 for help_item in self.user_helplist:
1812 self.helplist.insert(END, help_item[0])
1813 self.set_add_delete_state()
1814
1815 def help_source_selected(self, event):
1816 "Handle event for selecting additional help."
1817 self.set_add_delete_state()
1818
1819 def set_add_delete_state(self):
1820 "Toggle the state for the help list buttons based on list entries."
1821 if self.helplist.size() < 1: # No entries in list.
1822 self.button_helplist_edit['state'] = DISABLED
1823 self.button_helplist_remove['state'] = DISABLED
1824 else: # Some entries.
1825 if self.helplist.curselection(): # There currently is a selection.
1826 self.button_helplist_edit['state'] = NORMAL
1827 self.button_helplist_remove['state'] = NORMAL
1828 else: # There currently is not a selection.
1829 self.button_helplist_edit['state'] = DISABLED
1830 self.button_helplist_remove['state'] = DISABLED
1831
1832 def helplist_item_add(self):
1833 """Handle add button for the help list.
1834
1835 Query for name and location of new help sources and add
1836 them to the list.
1837 """
1838 help_source = HelpSource(self, 'New Help Source').result
1839 if help_source:
1840 self.user_helplist.append(help_source)
1841 self.helplist.insert(END, help_source[0])
1842 self.update_help_changes()
1843
1844 def helplist_item_edit(self):
1845 """Handle edit button for the help list.
1846
1847 Query with existing help source information and update
1848 config if the values are changed.
1849 """
1850 item_index = self.helplist.index(ANCHOR)
1851 help_source = self.user_helplist[item_index]
1852 new_help_source = HelpSource(
1853 self, 'Edit Help Source',
1854 menuitem=help_source[0],
1855 filepath=help_source[1],
1856 ).result
1857 if new_help_source and new_help_source != help_source:
1858 self.user_helplist[item_index] = new_help_source
1859 self.helplist.delete(item_index)
1860 self.helplist.insert(item_index, new_help_source[0])
1861 self.update_help_changes()
1862 self.set_add_delete_state() # Selected will be un-selected
1863
1864 def helplist_item_remove(self):
1865 """Handle remove button for the help list.
1866
1867 Delete the help list item from config.
1868 """
1869 item_index = self.helplist.index(ANCHOR)
1870 del(self.user_helplist[item_index])
1871 self.helplist.delete(item_index)
1872 self.update_help_changes()
1873 self.set_add_delete_state()
1874
1875 def update_help_changes(self):
1876 "Clear and rebuild the HelpFiles section in changes"
1877 changes['main']['HelpFiles'] = {}
1878 for num in range(1, len(self.user_helplist) + 1):
1879 changes.add_option(
1880 'main', 'HelpFiles', str(num),
1881 ';'.join(self.user_helplist[num-1][:2]))
1882
1883
Terry Jan Reedy0243bea2017-07-26 20:53:13 -04001884class VarTrace:
1885 """Maintain Tk variables trace state."""
1886
1887 def __init__(self):
1888 """Store Tk variables and callbacks.
1889
1890 untraced: List of tuples (var, callback)
1891 that do not have the callback attached
1892 to the Tk var.
1893 traced: List of tuples (var, callback) where
1894 that callback has been attached to the var.
1895 """
1896 self.untraced = []
1897 self.traced = []
1898
Terry Jan Reedyecc80b32017-07-28 18:36:30 -04001899 def clear(self):
1900 "Clear lists (for tests)."
Terry Jan Reedy9d7d9282017-08-07 15:20:03 -04001901 # Call after all tests in a module to avoid memory leaks.
Terry Jan Reedyecc80b32017-07-28 18:36:30 -04001902 self.untraced.clear()
1903 self.traced.clear()
1904
Terry Jan Reedy0243bea2017-07-26 20:53:13 -04001905 def add(self, var, callback):
1906 """Add (var, callback) tuple to untraced list.
1907
1908 Args:
1909 var: Tk variable instance.
Terry Jan Reedy02f88d22017-07-28 15:42:43 -04001910 callback: Either function name to be used as a callback
1911 or a tuple with IdleConf config-type, section, and
1912 option names used in the default callback.
Terry Jan Reedy0243bea2017-07-26 20:53:13 -04001913
1914 Return:
1915 Tk variable instance.
1916 """
1917 if isinstance(callback, tuple):
1918 callback = self.make_callback(var, callback)
1919 self.untraced.append((var, callback))
1920 return var
1921
1922 @staticmethod
1923 def make_callback(var, config):
1924 "Return default callback function to add values to changes instance."
1925 def default_callback(*params):
1926 "Add config values to changes instance."
1927 changes.add_option(*config, var.get())
1928 return default_callback
1929
1930 def attach(self):
1931 "Attach callback to all vars that are not traced."
1932 while self.untraced:
1933 var, callback = self.untraced.pop()
1934 var.trace_add('write', callback)
1935 self.traced.append((var, callback))
1936
1937 def detach(self):
1938 "Remove callback from traced vars."
1939 while self.traced:
1940 var, callback = self.traced.pop()
1941 var.trace_remove('write', var.trace_info()[0][1])
1942 self.untraced.append((var, callback))
1943
1944
Terry Jan Reedy02f88d22017-07-28 15:42:43 -04001945tracers = VarTrace()
1946
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001947help_common = '''\
1948When you click either the Apply or Ok buttons, settings in this
1949dialog that are different from IDLE's default are saved in
1950a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001951these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001952machine. Some do not take affect until IDLE is restarted.
1953[Cancel] only cancels changes made since the last save.
1954'''
1955help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001956 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001957Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001958The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001959be used with older IDLE releases if it is saved as a custom
1960theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001961''',
1962 'Keys': '''
1963Keys:
1964The IDLE Modern Unix key set is new in June 2016. It can only
1965be used with older IDLE releases if it is saved as a custom
1966key set, with a different name.
1967''',
terryjreedyaf683822017-06-27 23:02:19 -04001968 'Extensions': '''
1969Extensions:
1970
1971Autocomplete: Popupwait is milleseconds to wait after key char, without
1972cursor movement, before popping up completion box. Key char is '.' after
1973identifier or a '/' (or '\\' on Windows) within a string.
1974
1975FormatParagraph: Max-width is max chars in lines after re-formatting.
1976Use with paragraphs in both strings and comment blocks.
1977
1978ParenMatch: Style indicates what is highlighted when closer is entered:
1979'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1980'expression' (default) - also everything in between. Flash-delay is how
1981long to highlight if cursor is not moved (0 means forever).
1982'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001983}
1984
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001985
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001986def is_int(s):
1987 "Return 's is blank or represents an int'"
1988 if not s:
1989 return True
1990 try:
1991 int(s)
1992 return True
1993 except ValueError:
1994 return False
1995
1996
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001997class VerticalScrolledFrame(Frame):
1998 """A pure Tkinter vertically scrollable frame.
1999
2000 * Use the 'interior' attribute to place widgets inside the scrollable frame
2001 * Construct and pack/place/grid normally
2002 * This frame only allows vertical scrolling
2003 """
2004 def __init__(self, parent, *args, **kw):
2005 Frame.__init__(self, parent, *args, **kw)
2006
terryjreedye5bb1122017-07-05 00:54:55 -04002007 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002008 vscrollbar = Scrollbar(self, orient=VERTICAL)
2009 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
2010 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04002011 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002012 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
2013 vscrollbar.config(command=canvas.yview)
2014
terryjreedye5bb1122017-07-05 00:54:55 -04002015 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002016 canvas.xview_moveto(0)
2017 canvas.yview_moveto(0)
2018
terryjreedye5bb1122017-07-05 00:54:55 -04002019 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002020 self.interior = interior = Frame(canvas)
2021 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
2022
terryjreedye5bb1122017-07-05 00:54:55 -04002023 # Track changes to the canvas and frame width and sync them,
2024 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002025 def _configure_interior(event):
terryjreedye5bb1122017-07-05 00:54:55 -04002026 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002027 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
2028 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002029 interior.bind('<Configure>', _configure_interior)
2030
2031 def _configure_canvas(event):
2032 if interior.winfo_reqwidth() != canvas.winfo_width():
terryjreedye5bb1122017-07-05 00:54:55 -04002033 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002034 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
2035 canvas.bind('<Configure>', _configure_canvas)
2036
2037 return
2038
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002039
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00002040if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04002041 import unittest
2042 unittest.main('idlelib.idle_test.test_configdialog',
2043 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04002044 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04002045 run(ConfigDialog)