blob: 92bbc106344d9442532c39d537a8770a709b1a57 [file] [log] [blame]
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +00001"""IDLE Configuration Dialog: support user customization of IDLE by GUI
2
3Customize font faces, sizes, and colorization attributes. Set indentation
4defaults. Customize keybindings. Colorization and keybindings can be
5saved as user defined sets. Select startup options including shell/editor
6and default window size. Define additional help sources.
7
8Note that tab width in IDLE is currently fixed at eight due to Tk issues.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00009Refer to comments in EditorWindow autoindent code for details.
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +000010
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000011"""
csabellabac7d332017-06-26 17:46:26 -040012from tkinter import (Toplevel, Frame, LabelFrame, Listbox, Label, Button,
13 Entry, Text, Scale, Radiobutton, Checkbutton, Canvas,
14 StringVar, BooleanVar, IntVar, TRUE, FALSE,
15 TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE, NORMAL, DISABLED,
16 NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
Louie Lubb2bae82017-07-10 06:57:18 +080017 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
Terry Jan Reedyb331f802017-07-29 00:49:39 -040018from tkinter.ttk import Notebook, Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000019import tkinter.colorchooser as tkColorChooser
20import tkinter.font as tkFont
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021import tkinter.messagebox as tkMessageBox
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000022
terryjreedy349abd92017-07-07 16:00:57 -040023from idlelib.config import idleConf, ConfigChanges
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040024from idlelib.config_key import GetKeysDialog
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040025from idlelib.dynoption import DynOptionMenu
26from idlelib import macosx
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -040027from idlelib.query import SectionName, HelpSource
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040028from idlelib.tabbedpages import TabbedPageSet
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040029from idlelib.textview import view_text
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -040030
terryjreedy349abd92017-07-07 16:00:57 -040031changes = ConfigChanges()
32
csabella5b591542017-07-28 14:40:59 -040033
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000034class ConfigDialog(Toplevel):
csabella7eb58832017-07-04 21:30:58 -040035 """Config dialog for IDLE.
36 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000037
Terry Jan Reedycd567362014-10-17 01:31:35 -040038 def __init__(self, parent, title='', _htest=False, _utest=False):
csabella7eb58832017-07-04 21:30:58 -040039 """Show the tabbed dialog for user configuration.
40
csabella36329a42017-07-13 23:32:01 -040041 Args:
42 parent - parent of this dialog
43 title - string which is the title of this popup dialog
44 _htest - bool, change box location when running htest
45 _utest - bool, don't wait_window when running unittest
46
47 Note: Focus set on font page fontlist.
48
49 Methods:
50 create_widgets
51 cancel: Bound to DELETE_WINDOW protocol.
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040052 """
Steven M. Gavad721c482001-07-31 10:46:53 +000053 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040054 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040055 if _htest:
56 parent.instance_dict = {}
Louie Lu9b622fb2017-07-14 08:35:48 +080057 if not _utest:
58 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000059
Steven M. Gavad721c482001-07-31 10:46:53 +000060 self.configure(borderwidth=5)
Terry Jan Reedycd567362014-10-17 01:31:35 -040061 self.title(title or 'IDLE Preferences')
csabellabac7d332017-06-26 17:46:26 -040062 x = parent.winfo_rootx() + 20
63 y = parent.winfo_rooty() + (30 if not _htest else 150)
64 self.geometry(f'+{x}+{y}')
csabella7eb58832017-07-04 21:30:58 -040065 # Each theme element key is its display name.
66 # The first value of the tuple is the sample area tag name.
67 # The second value is the display name list sort index.
csabellabac7d332017-06-26 17:46:26 -040068 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040069 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000070 self.transient(parent)
csabellabac7d332017-06-26 17:46:26 -040071 self.protocol("WM_DELETE_WINDOW", self.cancel)
csabella9397e2a2017-07-30 13:34:25 -040072 self.fontpage.fontlist.focus_set()
csabella7eb58832017-07-04 21:30:58 -040073 # XXX Decide whether to keep or delete these key bindings.
74 # Key bindings for this dialog.
75 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
76 # self.bind('<Alt-a>', self.Apply) #apply changes, save
77 # self.bind('<F1>', self.Help) #context help
csabellabac7d332017-06-26 17:46:26 -040078 self.load_configs()
csabella5b591542017-07-28 14:40:59 -040079 # Avoid callbacks during load_configs.
80 tracers.attach()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000081
Terry Jan Reedycfa89502014-07-14 23:07:32 -040082 if not _utest:
Louie Lu9b622fb2017-07-14 08:35:48 +080083 self.grab_set()
Terry Jan Reedycfa89502014-07-14 23:07:32 -040084 self.wm_deiconify()
85 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000086
csabella5b591542017-07-28 14:40:59 -040087
csabellabac7d332017-06-26 17:46:26 -040088 def create_widgets(self):
csabella36329a42017-07-13 23:32:01 -040089 """Create and place widgets for tabbed dialog.
90
91 Widgets Bound to self:
92 tab_pages: TabbedPageSet
93
94 Methods:
95 create_page_font_tab
96 create_page_highlight
97 create_page_keys
98 create_page_general
99 create_page_extensions
100 create_action_buttons
101 load_configs: Load pages except for extensions.
csabella36329a42017-07-13 23:32:01 -0400102 activate_config_changes: Tell editors to reload.
103 """
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400104 self.note = note = Notebook(self, width=450, height=450)
csabella9397e2a2017-07-30 13:34:25 -0400105 self.highpage = self.create_page_highlight()
106 self.fontpage = FontPage(note, self.highpage)
107 self.keyspage = self.create_page_keys()
108 self.genpage = self.create_page_general()
109 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 Reedyb331f802017-07-29 00:49:39 -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 Reedyb1660802017-07-27 18:28:01 -0400119 def load_configs(self):
120 """Load configuration for each page.
121
122 Load configuration from default and user config files and populate
123 the widgets on the config dialog pages.
124
125 Methods:
126 load_font_cfg
127 load_tab_cfg
128 load_theme_cfg
129 load_key_cfg
130 load_general_cfg
131 """
csabella9397e2a2017-07-30 13:34:25 -0400132 #self.load_font_cfg()
133 #self.load_tab_cfg()
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400134 self.load_theme_cfg()
135 self.load_key_cfg()
136 self.load_general_cfg()
137 # note: extension page handled separately
138
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400139 def create_action_buttons(self):
csabella36329a42017-07-13 23:32:01 -0400140 """Return frame of action buttons for dialog.
141
142 Methods:
143 ok
144 apply
145 cancel
146 help
147
148 Widget Structure:
149 outer: Frame
150 buttons: Frame
151 (no assignment): Button (ok)
152 (no assignment): Button (apply)
153 (no assignment): Button (cancel)
154 (no assignment): Button (help)
155 (no assignment): Frame
156 """
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400157 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400158 # Changing the default padding on OSX results in unreadable
csabella7eb58832017-07-04 21:30:58 -0400159 # text in the buttons.
csabellabac7d332017-06-26 17:46:26 -0400160 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000161 else:
csabellabac7d332017-06-26 17:46:26 -0400162 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400163 outer = Frame(self, pady=2)
164 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400165 for txt, cmd in (
csabellabac7d332017-06-26 17:46:26 -0400166 ('Ok', self.ok),
167 ('Apply', self.apply),
168 ('Cancel', self.cancel),
169 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400170 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
csabellabac7d332017-06-26 17:46:26 -0400171 **padding_args).pack(side=LEFT, padx=5)
csabella7eb58832017-07-04 21:30:58 -0400172 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400173 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
174 buttons.pack(side=BOTTOM)
175 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400176
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400177 def ok(self):
178 """Apply config changes, then dismiss dialog.
179
180 Methods:
181 apply
182 destroy: inherited
183 """
184 self.apply()
185 self.destroy()
186
187 def apply(self):
188 """Apply config changes and leave dialog open.
189
190 Methods:
191 deactivate_current_config
192 save_all_changed_extensions
193 activate_config_changes
194 """
195 self.deactivate_current_config()
196 changes.save_all()
197 self.save_all_changed_extensions()
198 self.activate_config_changes()
199
200 def cancel(self):
201 """Dismiss config dialog.
202
203 Methods:
204 destroy: inherited
205 """
206 self.destroy()
207
208 def help(self):
209 """Create textview for config dialog help.
210
211 Attrbutes accessed:
212 tab_pages
213
214 Methods:
215 view_text: Method from textview module.
216 """
217 page = self.tab_pages._current_page
218 view_text(self, title='Help for IDLE preferences',
219 text=help_common+help_pages.get(page, ''))
220
Terry Jan Reedy77e97ca2017-07-24 00:18:25 -0400221
csabellabac7d332017-06-26 17:46:26 -0400222 def create_page_highlight(self):
csabella7eb58832017-07-04 21:30:58 -0400223 """Return frame of widgets for Highlighting tab.
224
csabella36329a42017-07-13 23:32:01 -0400225 Tk Variables:
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400226 color: Color of selected target.
csabella7eb58832017-07-04 21:30:58 -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.
csabella36329a42017-07-13 23:32:01 -0400230 Note: this has no callback.
csabella7eb58832017-07-04 21:30:58 -0400231 is_builtin_theme: Selector for built-in or custom theme.
232 highlight_target: Menu variable for the highlight tag target.
csabella36329a42017-07-13 23:32:01 -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 Reedya54a8f12017-07-21 01:06:58 -0400241 get_color: Invoke colorchooser [button_set_color].
242 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
csabella36329a42017-07-13 23:32:01 -0400243 set_highlight_target: set fg_bg_toggle, set_color_sample().
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400244 set_color_sample: Set frame background to target.
245 on_new_color_set: Set new color and add option.
csabella36329a42017-07-13 23:32:01 -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
254 Widget Structure: (*) widgets bound to self
255 frame
256 frame_custom: LabelFrame
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400257 (*)highlight_sample: Text
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400258 (*)frame_color_set: Frame
259 button_set_color: Button
csabella36329a42017-07-13 23:32:01 -0400260 (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
261 frame_fg_bg_toggle: Frame
262 (*)radio_fg: Radiobutton - fg_bg_toggle
263 (*)radio_bg: Radiobutton - fg_bg_toggle
264 button_save_custom_theme: Button
265 frame_theme: LabelFrame
266 theme_type_title: Label
267 (*)radio_theme_builtin: Radiobutton - is_builtin_theme
268 (*)radio_theme_custom: Radiobutton - is_builtin_theme
269 (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
270 (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
271 (*)button_delete_custom_theme: Button
272 (*)new_custom_theme: Label
csabella7eb58832017-07-04 21:30:58 -0400273 """
csabella36329a42017-07-13 23:32:01 -0400274 self.theme_elements={
275 'Normal Text': ('normal', '00'),
276 'Python Keywords': ('keyword', '01'),
277 'Python Definitions': ('definition', '02'),
278 'Python Builtins': ('builtin', '03'),
279 'Python Comments': ('comment', '04'),
280 'Python Strings': ('string', '05'),
281 'Selected Text': ('hilite', '06'),
282 'Found Text': ('hit', '07'),
283 'Cursor': ('cursor', '08'),
284 'Editor Breakpoint': ('break', '09'),
285 'Shell Normal Text': ('console', '10'),
286 'Shell Error Text': ('error', '11'),
287 'Shell Stdout Text': ('stdout', '12'),
288 'Shell Stderr Text': ('stderr', '13'),
289 }
Terry Jan Reedy22405332014-07-30 19:24:32 -0400290 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400291 self.builtin_theme = tracers.add(
292 StringVar(parent), self.var_changed_builtin_theme)
293 self.custom_theme = tracers.add(
294 StringVar(parent), self.var_changed_custom_theme)
csabellabac7d332017-06-26 17:46:26 -0400295 self.fg_bg_toggle = BooleanVar(parent)
csabella5b591542017-07-28 14:40:59 -0400296 self.color = tracers.add(
297 StringVar(parent), self.var_changed_color)
298 self.is_builtin_theme = tracers.add(
299 BooleanVar(parent), self.var_changed_is_builtin_theme)
300 self.highlight_target = tracers.add(
301 StringVar(parent), self.var_changed_highlight_target)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400302
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400303 # Widget creation:
304 # body frame and section frames
305 frame = Frame(self.note)
csabellabac7d332017-06-26 17:46:26 -0400306 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400307 text=' Custom Highlighting ')
csabellabac7d332017-06-26 17:46:26 -0400308 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400309 text=' Highlighting Theme ')
csabellabac7d332017-06-26 17:46:26 -0400310 #frame_custom
csabella9397e2a2017-07-30 13:34:25 -0400311 text = self.highlight_sample = frame.highlight_sample = Text(
csabellabac7d332017-06-26 17:46:26 -0400312 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400313 font=('courier', 12, ''), cursor='hand2', width=21, height=13,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400314 takefocus=FALSE, highlightthickness=0, wrap=NONE)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400315 text=self.highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400316 text.bind('<Double-Button-1>', lambda e: 'break')
317 text.bind('<B1-Motion>', lambda e: 'break')
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400318 text_and_tags=(('\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400319 ('#you can click here', 'comment'), ('\n', 'normal'),
320 ('#to choose items', 'comment'), ('\n', 'normal'),
321 ('def', 'keyword'), (' ', 'normal'),
322 ('func', 'definition'), ('(param):\n ', 'normal'),
323 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
324 ("'string'", 'string'), ('\n var1 = ', 'normal'),
325 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
326 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
327 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400328 ('None', 'keyword'), (')\n', 'normal'),
329 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400330 (' error ', 'error'), (' ', 'normal'),
331 ('cursor |', 'cursor'), ('\n ', 'normal'),
332 ('shell', 'console'), (' ', 'normal'),
333 ('stdout', 'stdout'), (' ', 'normal'),
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400334 ('stderr', 'stderr'), ('\n\n', 'normal'))
csabellabac7d332017-06-26 17:46:26 -0400335 for texttag in text_and_tags:
336 text.insert(END, texttag[0], texttag[1])
337 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400338 def tem(event, elem=element):
csabellabac7d332017-06-26 17:46:26 -0400339 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400340 text.tag_bind(
csabellabac7d332017-06-26 17:46:26 -0400341 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -0400342 text['state'] = DISABLED
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400343 self.frame_color_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
csabellabac7d332017-06-26 17:46:26 -0400344 frame_fg_bg_toggle = Frame(frame_custom)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400345 button_set_color = Button(
346 self.frame_color_set, text='Choose Color for :',
347 command=self.get_color, highlightthickness=0)
csabellabac7d332017-06-26 17:46:26 -0400348 self.opt_menu_highlight_target = DynOptionMenu(
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400349 self.frame_color_set, self.highlight_target, None,
csabellabac7d332017-06-26 17:46:26 -0400350 highlightthickness=0) #, command=self.set_highlight_targetBinding
351 self.radio_fg = Radiobutton(
352 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400353 text='Foreground', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400354 self.radio_bg=Radiobutton(
355 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400356 text='Background', command=self.set_color_sample_binding)
csabellabac7d332017-06-26 17:46:26 -0400357 self.fg_bg_toggle.set(1)
358 button_save_custom_theme = Button(
359 frame_custom, text='Save as New Custom Theme',
360 command=self.save_as_new_theme)
361 #frame_theme
362 theme_type_title = Label(frame_theme, text='Select : ')
363 self.radio_theme_builtin = Radiobutton(
364 frame_theme, variable=self.is_builtin_theme, value=1,
365 command=self.set_theme_type, text='a Built-in Theme')
366 self.radio_theme_custom = Radiobutton(
367 frame_theme, variable=self.is_builtin_theme, value=0,
368 command=self.set_theme_type, text='a Custom Theme')
369 self.opt_menu_theme_builtin = DynOptionMenu(
370 frame_theme, self.builtin_theme, None, command=None)
371 self.opt_menu_theme_custom=DynOptionMenu(
372 frame_theme, self.custom_theme, None, command=None)
373 self.button_delete_custom_theme=Button(
374 frame_theme, text='Delete Custom Theme',
375 command=self.delete_custom_theme)
376 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400377
Steven M. Gava952d0a52001-08-03 04:43:44 +0000378 ##widget packing
379 #body
csabellabac7d332017-06-26 17:46:26 -0400380 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
381 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
382 #frame_custom
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400383 self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
csabellabac7d332017-06-26 17:46:26 -0400384 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
Terry Jan Reedyd0969d62017-07-21 02:20:46 -0400385 self.highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400386 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedya54a8f12017-07-21 01:06:58 -0400387 button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
csabellabac7d332017-06-26 17:46:26 -0400388 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400389 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
csabellabac7d332017-06-26 17:46:26 -0400390 self.radio_fg.pack(side=LEFT, anchor=E)
391 self.radio_bg.pack(side=RIGHT, anchor=W)
392 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
393 #frame_theme
394 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
395 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
396 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
397 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
398 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
399 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500400 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000401 return frame
402
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400403 def load_theme_cfg(self):
404 """Load current configuration settings for the theme options.
405
406 Based on the is_builtin_theme toggle, the theme is set as
407 either builtin or custom and the initial widget values
408 reflect the current settings from idleConf.
409
410 Attributes updated:
411 is_builtin_theme: Set from idleConf.
412 opt_menu_theme_builtin: List of default themes from idleConf.
413 opt_menu_theme_custom: List of custom themes from idleConf.
414 radio_theme_custom: Disabled if there are no custom themes.
415 custom_theme: Message with additional information.
416 opt_menu_highlight_target: Create menu from self.theme_elements.
417
418 Methods:
419 set_theme_type
420 paint_theme_sample
421 set_highlight_target
422 """
423 # Set current theme type radiobutton.
424 self.is_builtin_theme.set(idleConf.GetOption(
425 'main', 'Theme', 'default', type='bool', default=1))
426 # Set current theme.
427 current_option = idleConf.CurrentTheme()
428 # Load available theme option menus.
429 if self.is_builtin_theme.get(): # Default theme selected.
430 item_list = idleConf.GetSectionList('default', 'highlight')
431 item_list.sort()
432 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
433 item_list = idleConf.GetSectionList('user', 'highlight')
434 item_list.sort()
435 if not item_list:
436 self.radio_theme_custom['state'] = DISABLED
437 self.custom_theme.set('- no custom themes -')
438 else:
439 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
440 else: # User theme selected.
441 item_list = idleConf.GetSectionList('user', 'highlight')
442 item_list.sort()
443 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
444 item_list = idleConf.GetSectionList('default', 'highlight')
445 item_list.sort()
446 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
447 self.set_theme_type()
448 # Load theme element option menu.
449 theme_names = list(self.theme_elements.keys())
450 theme_names.sort(key=lambda x: self.theme_elements[x][1])
451 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
452 self.paint_theme_sample()
453 self.set_highlight_target()
454
455 def var_changed_builtin_theme(self, *params):
456 """Process new builtin theme selection.
457
458 Add the changed theme's name to the changed_items and recreate
459 the sample with the values from the selected theme.
460 """
461 old_themes = ('IDLE Classic', 'IDLE New')
462 value = self.builtin_theme.get()
463 if value not in old_themes:
464 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
465 changes.add_option('main', 'Theme', 'name', old_themes[0])
466 changes.add_option('main', 'Theme', 'name2', value)
467 self.new_custom_theme.config(text='New theme, see Help',
468 fg='#500000')
469 else:
470 changes.add_option('main', 'Theme', 'name', value)
471 changes.add_option('main', 'Theme', 'name2', '')
472 self.new_custom_theme.config(text='', fg='black')
473 self.paint_theme_sample()
474
475 def var_changed_custom_theme(self, *params):
476 """Process new custom theme selection.
477
478 If a new custom theme is selected, add the name to the
479 changed_items and apply the theme to the sample.
480 """
481 value = self.custom_theme.get()
482 if value != '- no custom themes -':
483 changes.add_option('main', 'Theme', 'name', value)
484 self.paint_theme_sample()
485
486 def var_changed_is_builtin_theme(self, *params):
487 """Process toggle between builtin and custom theme.
488
489 Update the default toggle value and apply the newly
490 selected theme type.
491 """
492 value = self.is_builtin_theme.get()
493 changes.add_option('main', 'Theme', 'default', value)
494 if value:
495 self.var_changed_builtin_theme()
496 else:
497 self.var_changed_custom_theme()
498
499 def var_changed_color(self, *params):
500 "Process change to color choice."
501 self.on_new_color_set()
502
503 def var_changed_highlight_target(self, *params):
504 "Process selection of new target tag for highlighting."
505 self.set_highlight_target()
506
507 def set_theme_type(self):
508 """Set available screen options based on builtin or custom theme.
509
510 Attributes accessed:
511 is_builtin_theme
512
513 Attributes updated:
514 opt_menu_theme_builtin
515 opt_menu_theme_custom
516 button_delete_custom_theme
517 radio_theme_custom
518
519 Called from:
520 handler for radio_theme_builtin and radio_theme_custom
521 delete_custom_theme
522 create_new_theme
523 load_theme_cfg
524 """
525 if self.is_builtin_theme.get():
526 self.opt_menu_theme_builtin['state'] = NORMAL
527 self.opt_menu_theme_custom['state'] = DISABLED
528 self.button_delete_custom_theme['state'] = DISABLED
529 else:
530 self.opt_menu_theme_builtin['state'] = DISABLED
531 self.radio_theme_custom['state'] = NORMAL
532 self.opt_menu_theme_custom['state'] = NORMAL
533 self.button_delete_custom_theme['state'] = NORMAL
534
535 def get_color(self):
536 """Handle button to select a new color for the target tag.
537
538 If a new color is selected while using a builtin theme, a
539 name must be supplied to create a custom theme.
540
541 Attributes accessed:
542 highlight_target
543 frame_color_set
544 is_builtin_theme
545
546 Attributes updated:
547 color
548
549 Methods:
550 get_new_theme_name
551 create_new_theme
552 """
553 target = self.highlight_target.get()
554 prev_color = self.frame_color_set.cget('bg')
555 rgbTuplet, color_string = tkColorChooser.askcolor(
556 parent=self, title='Pick new color for : '+target,
557 initialcolor=prev_color)
558 if color_string and (color_string != prev_color):
559 # User didn't cancel and they chose a new color.
560 if self.is_builtin_theme.get(): # Current theme is a built-in.
561 message = ('Your changes will be saved as a new Custom Theme. '
562 'Enter a name for your new Custom Theme below.')
563 new_theme = self.get_new_theme_name(message)
564 if not new_theme: # User cancelled custom theme creation.
565 return
566 else: # Create new custom theme based on previously active theme.
567 self.create_new_theme(new_theme)
568 self.color.set(color_string)
569 else: # Current theme is user defined.
570 self.color.set(color_string)
571
572 def on_new_color_set(self):
573 "Display sample of new color selection on the dialog."
574 new_color=self.color.get()
575 self.frame_color_set.config(bg=new_color) # Set sample.
576 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
577 sample_element = self.theme_elements[self.highlight_target.get()][0]
578 self.highlight_sample.tag_config(sample_element, **{plane:new_color})
579 theme = self.custom_theme.get()
580 theme_element = sample_element + '-' + plane
581 changes.add_option('highlight', theme, theme_element, new_color)
582
583 def get_new_theme_name(self, message):
584 "Return name of new theme from query popup."
585 used_names = (idleConf.GetSectionList('user', 'highlight') +
586 idleConf.GetSectionList('default', 'highlight'))
587 new_theme = SectionName(
588 self, 'New Custom Theme', message, used_names).result
589 return new_theme
590
591 def save_as_new_theme(self):
592 """Prompt for new theme name and create the theme.
593
594 Methods:
595 get_new_theme_name
596 create_new_theme
597 """
598 new_theme_name = self.get_new_theme_name('New Theme Name:')
599 if new_theme_name:
600 self.create_new_theme(new_theme_name)
601
602 def create_new_theme(self, new_theme_name):
603 """Create a new custom theme with the given name.
604
605 Create the new theme based on the previously active theme
606 with the current changes applied. Once it is saved, then
607 activate the new theme.
608
609 Attributes accessed:
610 builtin_theme
611 custom_theme
612
613 Attributes updated:
614 opt_menu_theme_custom
615 is_builtin_theme
616
617 Method:
618 save_new_theme
619 set_theme_type
620 """
621 if self.is_builtin_theme.get():
622 theme_type = 'default'
623 theme_name = self.builtin_theme.get()
624 else:
625 theme_type = 'user'
626 theme_name = self.custom_theme.get()
627 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
628 # Apply any of the old theme's unsaved changes to the new theme.
629 if theme_name in changes['highlight']:
630 theme_changes = changes['highlight'][theme_name]
631 for element in theme_changes:
632 new_theme[element] = theme_changes[element]
633 # Save the new theme.
634 self.save_new_theme(new_theme_name, new_theme)
635 # Change GUI over to the new theme.
636 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
637 custom_theme_list.sort()
638 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
639 self.is_builtin_theme.set(0)
640 self.set_theme_type()
641
642 def set_highlight_target(self):
643 """Set fg/bg toggle and color based on highlight tag target.
644
645 Instance variables accessed:
646 highlight_target
647
648 Attributes updated:
649 radio_fg
650 radio_bg
651 fg_bg_toggle
652
653 Methods:
654 set_color_sample
655
656 Called from:
657 var_changed_highlight_target
658 load_theme_cfg
659 """
660 if self.highlight_target.get() == 'Cursor': # bg not possible
661 self.radio_fg['state'] = DISABLED
662 self.radio_bg['state'] = DISABLED
663 self.fg_bg_toggle.set(1)
664 else: # Both fg and bg can be set.
665 self.radio_fg['state'] = NORMAL
666 self.radio_bg['state'] = NORMAL
667 self.fg_bg_toggle.set(1)
668 self.set_color_sample()
669
670 def set_color_sample_binding(self, *args):
671 """Change color sample based on foreground/background toggle.
672
673 Methods:
674 set_color_sample
675 """
676 self.set_color_sample()
677
678 def set_color_sample(self):
679 """Set the color of the frame background to reflect the selected target.
680
681 Instance variables accessed:
682 theme_elements
683 highlight_target
684 fg_bg_toggle
685 highlight_sample
686
687 Attributes updated:
688 frame_color_set
689 """
690 # Set the color sample area.
691 tag = self.theme_elements[self.highlight_target.get()][0]
692 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
693 color = self.highlight_sample.tag_cget(tag, plane)
694 self.frame_color_set.config(bg=color)
695
696 def paint_theme_sample(self):
697 """Apply the theme colors to each element tag in the sample text.
698
699 Instance attributes accessed:
700 theme_elements
701 is_builtin_theme
702 builtin_theme
703 custom_theme
704
705 Attributes updated:
706 highlight_sample: Set the tag elements to the theme.
707
708 Methods:
709 set_color_sample
710
711 Called from:
712 var_changed_builtin_theme
713 var_changed_custom_theme
714 load_theme_cfg
715 """
716 if self.is_builtin_theme.get(): # Default theme
717 theme = self.builtin_theme.get()
718 else: # User theme
719 theme = self.custom_theme.get()
720 for element_title in self.theme_elements:
721 element = self.theme_elements[element_title][0]
722 colors = idleConf.GetHighlight(theme, element)
723 if element == 'cursor': # Cursor sample needs special painting.
724 colors['background'] = idleConf.GetHighlight(
725 theme, 'normal', fgBg='bg')
726 # Handle any unsaved changes to this theme.
727 if theme in changes['highlight']:
728 theme_dict = changes['highlight'][theme]
729 if element + '-foreground' in theme_dict:
730 colors['foreground'] = theme_dict[element + '-foreground']
731 if element + '-background' in theme_dict:
732 colors['background'] = theme_dict[element + '-background']
733 self.highlight_sample.tag_config(element, **colors)
734 self.set_color_sample()
735
736 def save_new_theme(self, theme_name, theme):
737 """Save a newly created theme to idleConf.
738
739 theme_name - string, the name of the new theme
740 theme - dictionary containing the new theme
741 """
742 if not idleConf.userCfg['highlight'].has_section(theme_name):
743 idleConf.userCfg['highlight'].add_section(theme_name)
744 for element in theme:
745 value = theme[element]
746 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
747
748 def delete_custom_theme(self):
749 """Handle event to delete custom theme.
750
751 The current theme is deactivated and the default theme is
752 activated. The custom theme is permanently removed from
753 the config file.
754
755 Attributes accessed:
756 custom_theme
757
758 Attributes updated:
759 radio_theme_custom
760 opt_menu_theme_custom
761 is_builtin_theme
762 builtin_theme
763
764 Methods:
765 deactivate_current_config
766 save_all_changed_extensions
767 activate_config_changes
768 set_theme_type
769 """
770 theme_name = self.custom_theme.get()
771 delmsg = 'Are you sure you wish to delete the theme %r ?'
772 if not tkMessageBox.askyesno(
773 'Delete Theme', delmsg % theme_name, parent=self):
774 return
775 self.deactivate_current_config()
776 # Remove theme from changes, config, and file.
777 changes.delete_section('highlight', theme_name)
778 # Reload user theme list.
779 item_list = idleConf.GetSectionList('user', 'highlight')
780 item_list.sort()
781 if not item_list:
782 self.radio_theme_custom['state'] = DISABLED
783 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
784 else:
785 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
786 # Revert to default theme.
787 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
788 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
789 # User can't back out of these changes, they must be applied now.
790 changes.save_all()
791 self.save_all_changed_extensions()
792 self.activate_config_changes()
793 self.set_theme_type()
794
795
csabellabac7d332017-06-26 17:46:26 -0400796 def create_page_keys(self):
csabella7eb58832017-07-04 21:30:58 -0400797 """Return frame of widgets for Keys tab.
798
csabella36329a42017-07-13 23:32:01 -0400799 Tk Variables:
csabella7eb58832017-07-04 21:30:58 -0400800 builtin_keys: Menu variable for built-in keybindings.
801 custom_keys: Menu variable for custom keybindings.
802 are_keys_builtin: Selector for built-in or custom keybindings.
803 keybinding: Action/key bindings.
csabella36329a42017-07-13 23:32:01 -0400804
805 Methods:
806 load_key_config: Set table.
807 load_keys_list: Reload active set.
808 keybinding_selected: Bound to list_bindings button release.
809 get_new_keys: Command for button_new_keys.
810 get_new_keys_name: Call popup.
811 create_new_key_set: Combine active keyset and changes.
812 set_keys_type: Command for are_keys_builtin.
813 delete_custom_keys: Command for button_delete_custom_keys.
814 save_as_new_key_set: Command for button_save_custom_keys.
815 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
816 deactivate_current_config: Remove keys bindings in editors.
817
818 Widget Structure: (*) widgets bound to self
819 frame
820 frame_custom: LabelFrame
821 frame_target: Frame
822 target_title: Label
823 scroll_target_y: Scrollbar
824 scroll_target_x: Scrollbar
825 (*)list_bindings: ListBox
826 (*)button_new_keys: Button
827 frame_key_sets: LabelFrame
828 frames[0]: Frame
829 (*)radio_keys_builtin: Radiobutton - are_keys_builtin
830 (*)radio_keys_custom: Radiobutton - are_keys_builtin
831 (*)opt_menu_keys_builtin: DynOptionMenu - builtin_keys
832 (*)opt_menu_keys_custom: DynOptionMenu - custom_keys
833 (*)new_custom_keys: Label
834 frames[1]: Frame
835 (*)button_delete_custom_keys: Button
836 button_save_custom_keys: Button
csabella7eb58832017-07-04 21:30:58 -0400837 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400838 parent = self.parent
csabella5b591542017-07-28 14:40:59 -0400839 self.builtin_keys = tracers.add(
840 StringVar(parent), self.var_changed_builtin_keys)
841 self.custom_keys = tracers.add(
842 StringVar(parent), self.var_changed_custom_keys)
843 self.are_keys_builtin = tracers.add(
844 BooleanVar(parent), self.var_changed_are_keys_builtin)
845 self.keybinding = tracers.add(
846 StringVar(parent), self.var_changed_keybinding)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400847
Terry Jan Reedyb331f802017-07-29 00:49:39 -0400848 # Widget creation:
849 # body and section frames.
850 frame = Frame(self.note)
csabellabac7d332017-06-26 17:46:26 -0400851 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400852 frame, borderwidth=2, relief=GROOVE,
853 text=' Custom Key Bindings ')
csabellabac7d332017-06-26 17:46:26 -0400854 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400855 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
csabellabac7d332017-06-26 17:46:26 -0400856 #frame_custom
857 frame_target = Frame(frame_custom)
858 target_title = Label(frame_target, text='Action - Key(s)')
859 scroll_target_y = Scrollbar(frame_target)
860 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
861 self.list_bindings = Listbox(
862 frame_target, takefocus=FALSE, exportselection=FALSE)
863 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
864 scroll_target_y.config(command=self.list_bindings.yview)
865 scroll_target_x.config(command=self.list_bindings.xview)
866 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
867 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
868 self.button_new_keys = Button(
869 frame_custom, text='Get New Keys for Selection',
870 command=self.get_new_keys, state=DISABLED)
871 #frame_key_sets
872 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +0000873 for i in range(2)]
csabellabac7d332017-06-26 17:46:26 -0400874 self.radio_keys_builtin = Radiobutton(
875 frames[0], variable=self.are_keys_builtin, value=1,
876 command=self.set_keys_type, text='Use a Built-in Key Set')
877 self.radio_keys_custom = Radiobutton(
878 frames[0], variable=self.are_keys_builtin, value=0,
879 command=self.set_keys_type, text='Use a Custom Key Set')
880 self.opt_menu_keys_builtin = DynOptionMenu(
881 frames[0], self.builtin_keys, None, command=None)
882 self.opt_menu_keys_custom = DynOptionMenu(
883 frames[0], self.custom_keys, None, command=None)
884 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400885 frames[1], text='Delete Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -0400886 command=self.delete_custom_keys)
887 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400888 frames[1], text='Save as New Custom Key Set',
csabellabac7d332017-06-26 17:46:26 -0400889 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400890 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400891
Steven M. Gava60fc7072001-08-04 13:58:22 +0000892 ##widget packing
893 #body
csabellabac7d332017-06-26 17:46:26 -0400894 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
895 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
896 #frame_custom
897 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
898 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +0000899 #frame target
csabellabac7d332017-06-26 17:46:26 -0400900 frame_target.columnconfigure(0, weight=1)
901 frame_target.rowconfigure(1, weight=1)
902 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
903 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
904 scroll_target_y.grid(row=1, column=1, sticky=NS)
905 scroll_target_x.grid(row=2, column=0, sticky=EW)
906 #frame_key_sets
907 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
908 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
909 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
910 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400911 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
csabellabac7d332017-06-26 17:46:26 -0400912 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
913 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +0000914 frames[0].pack(side=TOP, fill=BOTH, expand=True)
915 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000916 return frame
917
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400918 def load_key_cfg(self):
919 "Load current configuration settings for the keybinding options."
920 # Set current keys type radiobutton.
921 self.are_keys_builtin.set(idleConf.GetOption(
922 'main', 'Keys', 'default', type='bool', default=1))
923 # Set current keys.
924 current_option = idleConf.CurrentKeys()
925 # Load available keyset option menus.
926 if self.are_keys_builtin.get(): # Default theme selected.
927 item_list = idleConf.GetSectionList('default', 'keys')
928 item_list.sort()
929 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
930 item_list = idleConf.GetSectionList('user', 'keys')
931 item_list.sort()
932 if not item_list:
933 self.radio_keys_custom['state'] = DISABLED
934 self.custom_keys.set('- no custom keys -')
935 else:
936 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
937 else: # User key set selected.
938 item_list = idleConf.GetSectionList('user', 'keys')
939 item_list.sort()
940 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
941 item_list = idleConf.GetSectionList('default', 'keys')
942 item_list.sort()
943 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
944 self.set_keys_type()
945 # Load keyset element list.
946 keyset_name = idleConf.CurrentKeys()
947 self.load_keys_list(keyset_name)
948
Terry Jan Reedyb1660802017-07-27 18:28:01 -0400949 def var_changed_builtin_keys(self, *params):
950 "Process selection of builtin key set."
951 old_keys = (
952 'IDLE Classic Windows',
953 'IDLE Classic Unix',
954 'IDLE Classic Mac',
955 'IDLE Classic OSX',
956 )
957 value = self.builtin_keys.get()
958 if value not in old_keys:
959 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
960 changes.add_option('main', 'Keys', 'name', old_keys[0])
961 changes.add_option('main', 'Keys', 'name2', value)
962 self.new_custom_keys.config(text='New key set, see Help',
963 fg='#500000')
964 else:
965 changes.add_option('main', 'Keys', 'name', value)
966 changes.add_option('main', 'Keys', 'name2', '')
967 self.new_custom_keys.config(text='', fg='black')
968 self.load_keys_list(value)
969
970 def var_changed_custom_keys(self, *params):
971 "Process selection of custom key set."
972 value = self.custom_keys.get()
973 if value != '- no custom keys -':
974 changes.add_option('main', 'Keys', 'name', value)
975 self.load_keys_list(value)
976
977 def var_changed_are_keys_builtin(self, *params):
978 "Process toggle between builtin key set and custom key set."
979 value = self.are_keys_builtin.get()
980 changes.add_option('main', 'Keys', 'default', value)
981 if value:
982 self.var_changed_builtin_keys()
983 else:
984 self.var_changed_custom_keys()
985
986 def var_changed_keybinding(self, *params):
987 "Store change to a keybinding."
988 value = self.keybinding.get()
989 key_set = self.custom_keys.get()
990 event = self.list_bindings.get(ANCHOR).split()[0]
991 if idleConf.IsCoreBinding(event):
992 changes.add_option('keys', key_set, event, value)
993 else: # Event is an extension binding.
994 ext_name = idleConf.GetExtnNameForEvent(event)
995 ext_keybind_section = ext_name + '_cfgBindings'
996 changes.add_option('extensions', ext_keybind_section, event, value)
997
998 def set_keys_type(self):
999 "Set available screen options based on builtin or custom key set."
1000 if self.are_keys_builtin.get():
1001 self.opt_menu_keys_builtin['state'] = NORMAL
1002 self.opt_menu_keys_custom['state'] = DISABLED
1003 self.button_delete_custom_keys['state'] = DISABLED
1004 else:
1005 self.opt_menu_keys_builtin['state'] = DISABLED
1006 self.radio_keys_custom['state'] = NORMAL
1007 self.opt_menu_keys_custom['state'] = NORMAL
1008 self.button_delete_custom_keys['state'] = NORMAL
1009
1010 def get_new_keys(self):
1011 """Handle event to change key binding for selected line.
1012
1013 A selection of a key/binding in the list of current
1014 bindings pops up a dialog to enter a new binding. If
1015 the current key set is builtin and a binding has
1016 changed, then a name for a custom key set needs to be
1017 entered for the change to be applied.
1018 """
1019 list_index = self.list_bindings.index(ANCHOR)
1020 binding = self.list_bindings.get(list_index)
1021 bind_name = binding.split()[0]
1022 if self.are_keys_builtin.get():
1023 current_key_set_name = self.builtin_keys.get()
1024 else:
1025 current_key_set_name = self.custom_keys.get()
1026 current_bindings = idleConf.GetCurrentKeySet()
1027 if current_key_set_name in changes['keys']: # unsaved changes
1028 key_set_changes = changes['keys'][current_key_set_name]
1029 for event in key_set_changes:
1030 current_bindings[event] = key_set_changes[event].split()
1031 current_key_sequences = list(current_bindings.values())
1032 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1033 current_key_sequences).result
1034 if new_keys:
1035 if self.are_keys_builtin.get(): # Current key set is a built-in.
1036 message = ('Your changes will be saved as a new Custom Key Set.'
1037 ' Enter a name for your new Custom Key Set below.')
1038 new_keyset = self.get_new_keys_name(message)
1039 if not new_keyset: # User cancelled custom key set creation.
1040 self.list_bindings.select_set(list_index)
1041 self.list_bindings.select_anchor(list_index)
1042 return
1043 else: # Create new custom key set based on previously active key set.
1044 self.create_new_key_set(new_keyset)
1045 self.list_bindings.delete(list_index)
1046 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
1047 self.list_bindings.select_set(list_index)
1048 self.list_bindings.select_anchor(list_index)
1049 self.keybinding.set(new_keys)
1050 else:
1051 self.list_bindings.select_set(list_index)
1052 self.list_bindings.select_anchor(list_index)
1053
1054 def get_new_keys_name(self, message):
1055 "Return new key set name from query popup."
1056 used_names = (idleConf.GetSectionList('user', 'keys') +
1057 idleConf.GetSectionList('default', 'keys'))
1058 new_keyset = SectionName(
1059 self, 'New Custom Key Set', message, used_names).result
1060 return new_keyset
1061
1062 def save_as_new_key_set(self):
1063 "Prompt for name of new key set and save changes using that name."
1064 new_keys_name = self.get_new_keys_name('New Key Set Name:')
1065 if new_keys_name:
1066 self.create_new_key_set(new_keys_name)
1067
1068 def keybinding_selected(self, event):
1069 "Activate button to assign new keys to selected action."
1070 self.button_new_keys['state'] = NORMAL
1071
1072 def create_new_key_set(self, new_key_set_name):
1073 """Create a new custom key set with the given name.
1074
1075 Create the new key set based on the previously active set
1076 with the current changes applied. Once it is saved, then
1077 activate the new key set.
1078 """
1079 if self.are_keys_builtin.get():
1080 prev_key_set_name = self.builtin_keys.get()
1081 else:
1082 prev_key_set_name = self.custom_keys.get()
1083 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
1084 new_keys = {}
1085 for event in prev_keys: # Add key set to changed items.
1086 event_name = event[2:-2] # Trim off the angle brackets.
1087 binding = ' '.join(prev_keys[event])
1088 new_keys[event_name] = binding
1089 # Handle any unsaved changes to prev key set.
1090 if prev_key_set_name in changes['keys']:
1091 key_set_changes = changes['keys'][prev_key_set_name]
1092 for event in key_set_changes:
1093 new_keys[event] = key_set_changes[event]
1094 # Save the new key set.
1095 self.save_new_key_set(new_key_set_name, new_keys)
1096 # Change GUI over to the new key set.
1097 custom_key_list = idleConf.GetSectionList('user', 'keys')
1098 custom_key_list.sort()
1099 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
1100 self.are_keys_builtin.set(0)
1101 self.set_keys_type()
1102
1103 def load_keys_list(self, keyset_name):
1104 """Reload the list of action/key binding pairs for the active key set.
1105
1106 An action/key binding can be selected to change the key binding.
1107 """
1108 reselect = 0
1109 if self.list_bindings.curselection():
1110 reselect = 1
1111 list_index = self.list_bindings.index(ANCHOR)
1112 keyset = idleConf.GetKeySet(keyset_name)
1113 bind_names = list(keyset.keys())
1114 bind_names.sort()
1115 self.list_bindings.delete(0, END)
1116 for bind_name in bind_names:
1117 key = ' '.join(keyset[bind_name])
1118 bind_name = bind_name[2:-2] # Trim off the angle brackets.
1119 if keyset_name in changes['keys']:
1120 # Handle any unsaved changes to this key set.
1121 if bind_name in changes['keys'][keyset_name]:
1122 key = changes['keys'][keyset_name][bind_name]
1123 self.list_bindings.insert(END, bind_name+' - '+key)
1124 if reselect:
1125 self.list_bindings.see(list_index)
1126 self.list_bindings.select_set(list_index)
1127 self.list_bindings.select_anchor(list_index)
1128
1129 def save_new_key_set(self, keyset_name, keyset):
1130 """Save a newly created core key set.
1131
1132 keyset_name - string, the name of the new key set
1133 keyset - dictionary containing the new key set
1134 """
1135 if not idleConf.userCfg['keys'].has_section(keyset_name):
1136 idleConf.userCfg['keys'].add_section(keyset_name)
1137 for event in keyset:
1138 value = keyset[event]
1139 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
1140
1141 def delete_custom_keys(self):
1142 """Handle event to delete a custom key set.
1143
1144 Applying the delete deactivates the current configuration and
1145 reverts to the default. The custom key set is permanently
1146 deleted from the config file.
1147 """
1148 keyset_name=self.custom_keys.get()
1149 delmsg = 'Are you sure you wish to delete the key set %r ?'
1150 if not tkMessageBox.askyesno(
1151 'Delete Key Set', delmsg % keyset_name, parent=self):
1152 return
1153 self.deactivate_current_config()
1154 # Remove key set from changes, config, and file.
1155 changes.delete_section('keys', keyset_name)
1156 # Reload user key set list.
1157 item_list = idleConf.GetSectionList('user', 'keys')
1158 item_list.sort()
1159 if not item_list:
1160 self.radio_keys_custom['state'] = DISABLED
1161 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
1162 else:
1163 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
1164 # Revert to default key set.
1165 self.are_keys_builtin.set(idleConf.defaultCfg['main']
1166 .Get('Keys', 'default'))
1167 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
1168 or idleConf.default_keys())
1169 # User can't back out of these changes, they must be applied now.
1170 changes.save_all()
1171 self.save_all_changed_extensions()
1172 self.activate_config_changes()
1173 self.set_keys_type()
1174
1175 def deactivate_current_config(self):
1176 """Remove current key bindings.
1177
1178 Iterate over window instances defined in parent and remove
1179 the keybindings.
1180 """
1181 # Before a config is saved, some cleanup of current
1182 # config must be done - remove the previous keybindings.
1183 win_instances = self.parent.instance_dict.keys()
1184 for instance in win_instances:
1185 instance.RemoveKeybindings()
1186
1187 def activate_config_changes(self):
1188 """Apply configuration changes to current windows.
1189
1190 Dynamically update the current parent window instances
1191 with some of the configuration changes.
1192 """
1193 win_instances = self.parent.instance_dict.keys()
1194 for instance in win_instances:
1195 instance.ResetColorizer()
1196 instance.ResetFont()
1197 instance.set_notabs_indentwidth()
1198 instance.ApplyKeybindings()
1199 instance.reset_help_menu_entries()
1200
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001201
csabellabac7d332017-06-26 17:46:26 -04001202 def create_page_general(self):
csabella7eb58832017-07-04 21:30:58 -04001203 """Return frame of widgets for General tab.
1204
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001205 Enable users to provisionally change general options. Function
1206 load_general_cfg intializes tk variables and helplist using
1207 idleConf. Radiobuttons startup_shell_on and startup_editor_on
1208 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
1209 set var autosave. Entry boxes win_width_int and win_height_int
1210 set var win_width and win_height. Setting var_name invokes the
csabella5b591542017-07-28 14:40:59 -04001211 default callback that adds option to changes.
csabella36329a42017-07-13 23:32:01 -04001212
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001213 Helplist: load_general_cfg loads list user_helplist with
1214 name, position pairs and copies names to listbox helplist.
1215 Clicking a name invokes help_source selected. Clicking
1216 button_helplist_name invokes helplist_item_name, which also
1217 changes user_helplist. These functions all call
1218 set_add_delete_state. All but load call update_help_changes to
1219 rewrite changes['main']['HelpFiles'].
csabella36329a42017-07-13 23:32:01 -04001220
1221 Widget Structure: (*) widgets bound to self
1222 frame
1223 frame_run: LabelFrame
1224 startup_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001225 (*)startup_editor_on: Radiobutton - startup_edit
1226 (*)startup_shell_on: Radiobutton - startup_edit
csabella36329a42017-07-13 23:32:01 -04001227 frame_save: LabelFrame
1228 run_save_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001229 (*)save_ask_on: Radiobutton - autosave
1230 (*)save_auto_on: Radiobutton - autosave
csabella36329a42017-07-13 23:32:01 -04001231 frame_win_size: LabelFrame
1232 win_size_title: Label
1233 win_width_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001234 (*)win_width_int: Entry - win_width
csabella36329a42017-07-13 23:32:01 -04001235 win_height_title: Label
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001236 (*)win_height_int: Entry - win_height
csabella36329a42017-07-13 23:32:01 -04001237 frame_help: LabelFrame
1238 frame_helplist: Frame
1239 frame_helplist_buttons: Frame
1240 (*)button_helplist_edit
1241 (*)button_helplist_add
1242 (*)button_helplist_remove
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001243 (*)helplist: ListBox
csabella36329a42017-07-13 23:32:01 -04001244 scroll_helplist: Scrollbar
csabella7eb58832017-07-04 21:30:58 -04001245 """
Terry Jan Reedy22405332014-07-30 19:24:32 -04001246 parent = self.parent
csabella5b591542017-07-28 14:40:59 -04001247 self.startup_edit = tracers.add(
1248 IntVar(parent), ('main', 'General', 'editor-on-startup'))
1249 self.autosave = tracers.add(
1250 IntVar(parent), ('main', 'General', 'autosave'))
1251 self.win_width = tracers.add(
1252 StringVar(parent), ('main', 'EditorWindow', 'width'))
1253 self.win_height = tracers.add(
1254 StringVar(parent), ('main', 'EditorWindow', 'height'))
Terry Jan Reedy22405332014-07-30 19:24:32 -04001255
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001256 # Create widgets:
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001257 # body and section frames.
1258 frame = Frame(self.note)
csabellabac7d332017-06-26 17:46:26 -04001259 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001260 text=' Startup Preferences ')
csabellabac7d332017-06-26 17:46:26 -04001261 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
1262 text=' autosave Preferences ')
1263 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
1264 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001265 text=' Additional Help Sources ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001266 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001267 startup_title = Label(frame_run, text='At Startup')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001268 self.startup_editor_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001269 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001270 text="Open Edit Window")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001271 self.startup_shell_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001272 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001273 text='Open Shell Window')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001274 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001275 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001276 self.save_ask_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001277 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001278 text="Prompt to Save")
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001279 self.save_auto_on = Radiobutton(
csabellabac7d332017-06-26 17:46:26 -04001280 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001281 text='No Prompt')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001282 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001283 win_size_title = Label(
1284 frame_win_size, text='Initial Window Size (in characters)')
1285 win_width_title = Label(frame_win_size, text='Width')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001286 self.win_width_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001287 frame_win_size, textvariable=self.win_width, width=3)
1288 win_height_title = Label(frame_win_size, text='Height')
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001289 self.win_height_int = Entry(
csabellabac7d332017-06-26 17:46:26 -04001290 frame_win_size, textvariable=self.win_height, width=3)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001291 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001292 frame_helplist = Frame(frame_help)
1293 frame_helplist_buttons = Frame(frame_helplist)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001294 self.helplist = Listbox(
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001295 frame_helplist, height=5, takefocus=True,
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001296 exportselection=FALSE)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001297 scroll_helplist = Scrollbar(frame_helplist)
1298 scroll_helplist['command'] = self.helplist.yview
1299 self.helplist['yscrollcommand'] = scroll_helplist.set
1300 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
csabellabac7d332017-06-26 17:46:26 -04001301 self.button_helplist_edit = Button(
1302 frame_helplist_buttons, text='Edit', state=DISABLED,
1303 width=8, command=self.helplist_item_edit)
1304 self.button_helplist_add = Button(
1305 frame_helplist_buttons, text='Add',
1306 width=8, command=self.helplist_item_add)
1307 self.button_helplist_remove = Button(
1308 frame_helplist_buttons, text='Remove', state=DISABLED,
1309 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -04001310
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001311 # Pack widgets:
1312 # body.
csabellabac7d332017-06-26 17:46:26 -04001313 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
1314 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
1315 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
1316 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001317 # frame_run.
csabellabac7d332017-06-26 17:46:26 -04001318 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001319 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1320 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1321 # frame_save.
csabellabac7d332017-06-26 17:46:26 -04001322 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001323 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1324 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
1325 # frame_win_size.
csabellabac7d332017-06-26 17:46:26 -04001326 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001327 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001328 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001329 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
csabellabac7d332017-06-26 17:46:26 -04001330 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001331 # frame_help.
csabellabac7d332017-06-26 17:46:26 -04001332 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
1333 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1334 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001335 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
csabellabac7d332017-06-26 17:46:26 -04001336 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
1337 self.button_helplist_add.pack(side=TOP, anchor=W)
1338 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001339
Steven M. Gava952d0a52001-08-03 04:43:44 +00001340 return frame
1341
Terry Jan Reedy2bc8f0e2017-07-26 20:54:40 -04001342 def load_general_cfg(self):
1343 "Load current configuration settings for the general options."
1344 # Set startup state.
1345 self.startup_edit.set(idleConf.GetOption(
1346 'main', 'General', 'editor-on-startup', default=0, type='bool'))
1347 # Set autosave state.
1348 self.autosave.set(idleConf.GetOption(
1349 'main', 'General', 'autosave', default=0, type='bool'))
1350 # Set initial window size.
1351 self.win_width.set(idleConf.GetOption(
1352 'main', 'EditorWindow', 'width', type='int'))
1353 self.win_height.set(idleConf.GetOption(
1354 'main', 'EditorWindow', 'height', type='int'))
1355 # Set additional help sources.
1356 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1357 self.helplist.delete(0, 'end')
1358 for help_item in self.user_helplist:
1359 self.helplist.insert(END, help_item[0])
1360 self.set_add_delete_state()
1361
1362 def var_changed_startup_edit(self, *params):
1363 "Store change to toggle for starting IDLE in the editor or shell."
1364 value = self.startup_edit.get()
1365 changes.add_option('main', 'General', 'editor-on-startup', value)
1366
1367 def var_changed_autosave(self, *params):
1368 "Store change to autosave."
1369 value = self.autosave.get()
1370 changes.add_option('main', 'General', 'autosave', value)
1371
1372 def var_changed_win_width(self, *params):
1373 "Store change to window width."
1374 value = self.win_width.get()
1375 changes.add_option('main', 'EditorWindow', 'width', value)
1376
1377 def var_changed_win_height(self, *params):
1378 "Store change to window height."
1379 value = self.win_height.get()
1380 changes.add_option('main', 'EditorWindow', 'height', value)
1381
1382 def help_source_selected(self, event):
1383 "Handle event for selecting additional help."
1384 self.set_add_delete_state()
1385
1386 def set_add_delete_state(self):
1387 "Toggle the state for the help list buttons based on list entries."
1388 if self.helplist.size() < 1: # No entries in list.
1389 self.button_helplist_edit['state'] = DISABLED
1390 self.button_helplist_remove['state'] = DISABLED
1391 else: # Some entries.
1392 if self.helplist.curselection(): # There currently is a selection.
1393 self.button_helplist_edit['state'] = NORMAL
1394 self.button_helplist_remove['state'] = NORMAL
1395 else: # There currently is not a selection.
1396 self.button_helplist_edit['state'] = DISABLED
1397 self.button_helplist_remove['state'] = DISABLED
1398
1399 def helplist_item_add(self):
1400 """Handle add button for the help list.
1401
1402 Query for name and location of new help sources and add
1403 them to the list.
1404 """
1405 help_source = HelpSource(self, 'New Help Source').result
1406 if help_source:
1407 self.user_helplist.append(help_source)
1408 self.helplist.insert(END, help_source[0])
1409 self.update_help_changes()
1410
1411 def helplist_item_edit(self):
1412 """Handle edit button for the help list.
1413
1414 Query with existing help source information and update
1415 config if the values are changed.
1416 """
1417 item_index = self.helplist.index(ANCHOR)
1418 help_source = self.user_helplist[item_index]
1419 new_help_source = HelpSource(
1420 self, 'Edit Help Source',
1421 menuitem=help_source[0],
1422 filepath=help_source[1],
1423 ).result
1424 if new_help_source and new_help_source != help_source:
1425 self.user_helplist[item_index] = new_help_source
1426 self.helplist.delete(item_index)
1427 self.helplist.insert(item_index, new_help_source[0])
1428 self.update_help_changes()
1429 self.set_add_delete_state() # Selected will be un-selected
1430
1431 def helplist_item_remove(self):
1432 """Handle remove button for the help list.
1433
1434 Delete the help list item from config.
1435 """
1436 item_index = self.helplist.index(ANCHOR)
1437 del(self.user_helplist[item_index])
1438 self.helplist.delete(item_index)
1439 self.update_help_changes()
1440 self.set_add_delete_state()
1441
1442 def update_help_changes(self):
1443 "Clear and rebuild the HelpFiles section in changes"
1444 changes['main']['HelpFiles'] = {}
1445 for num in range(1, len(self.user_helplist) + 1):
1446 changes.add_option(
1447 'main', 'HelpFiles', str(num),
1448 ';'.join(self.user_helplist[num-1][:2]))
1449
1450
csabellabac7d332017-06-26 17:46:26 -04001451 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001452 """Part of the config dialog used for configuring IDLE extensions.
1453
1454 This code is generic - it works for any and all IDLE extensions.
1455
1456 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001457 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001458 GUI interface to change the configuration values, and saves the
1459 changes using idleConf.
1460
1461 Not all changes take effect immediately - some may require restarting IDLE.
1462 This depends on each extension's implementation.
1463
1464 All values are treated as text, and it is up to the user to supply
1465 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001466 which are boolean, and can be toggled with a True/False button.
csabella36329a42017-07-13 23:32:01 -04001467
1468 Methods:
1469 load_extentions:
1470 extension_selected: Handle selection from list.
1471 create_extension_frame: Hold widgets for one extension.
1472 set_extension_value: Set in userCfg['extensions'].
1473 save_all_changed_extensions: Call extension page Save().
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001474 """
1475 parent = self.parent
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001476 frame = Frame(self.note)
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001477 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1478 self.ext_userCfg = idleConf.userCfg['extensions']
1479 self.is_int = self.register(is_int)
1480 self.load_extensions()
csabella7eb58832017-07-04 21:30:58 -04001481 # Create widgets - a listbox shows all available extensions, with the
1482 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001483 self.extension_names = StringVar(self)
1484 frame.rowconfigure(0, weight=1)
1485 frame.columnconfigure(2, weight=1)
1486 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1487 selectmode='browse')
1488 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1489 scroll = Scrollbar(frame, command=self.extension_list.yview)
1490 self.extension_list.yscrollcommand=scroll.set
1491 self.details_frame = LabelFrame(frame, width=250, height=250)
1492 self.extension_list.grid(column=0, row=0, sticky='nws')
1493 scroll.grid(column=1, row=0, sticky='ns')
1494 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1495 frame.configure(padx=10, pady=10)
1496 self.config_frame = {}
1497 self.current_extension = None
1498
1499 self.outerframe = self # TEMPORARY
1500 self.tabbed_page_set = self.extension_list # TEMPORARY
1501
csabella7eb58832017-07-04 21:30:58 -04001502 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001503 ext_names = ''
1504 for ext_name in sorted(self.extensions):
1505 self.create_extension_frame(ext_name)
1506 ext_names = ext_names + '{' + ext_name + '} '
1507 self.extension_names.set(ext_names)
1508 self.extension_list.selection_set(0)
1509 self.extension_selected(None)
1510
Terry Jan Reedyb331f802017-07-29 00:49:39 -04001511 return frame
1512
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001513 def load_extensions(self):
1514 "Fill self.extensions with data from the default and user configs."
1515 self.extensions = {}
1516 for ext_name in idleConf.GetExtensions(active_only=False):
1517 self.extensions[ext_name] = []
1518
1519 for ext_name in self.extensions:
1520 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1521
csabella7eb58832017-07-04 21:30:58 -04001522 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001523 enables = [opt_name for opt_name in opt_list
1524 if opt_name.startswith('enable')]
1525 for opt_name in enables:
1526 opt_list.remove(opt_name)
1527 opt_list = enables + opt_list
1528
1529 for opt_name in opt_list:
1530 def_str = self.ext_defaultCfg.Get(
1531 ext_name, opt_name, raw=True)
1532 try:
1533 def_obj = {'True':True, 'False':False}[def_str]
1534 opt_type = 'bool'
1535 except KeyError:
1536 try:
1537 def_obj = int(def_str)
1538 opt_type = 'int'
1539 except ValueError:
1540 def_obj = def_str
1541 opt_type = None
1542 try:
1543 value = self.ext_userCfg.Get(
1544 ext_name, opt_name, type=opt_type, raw=True,
1545 default=def_obj)
csabella7eb58832017-07-04 21:30:58 -04001546 except ValueError: # Need this until .Get fixed.
1547 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001548 var = StringVar(self)
1549 var.set(str(value))
1550
1551 self.extensions[ext_name].append({'name': opt_name,
1552 'type': opt_type,
1553 'default': def_str,
1554 'value': value,
1555 'var': var,
1556 })
1557
1558 def extension_selected(self, event):
csabella7eb58832017-07-04 21:30:58 -04001559 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001560 newsel = self.extension_list.curselection()
1561 if newsel:
1562 newsel = self.extension_list.get(newsel)
1563 if newsel is None or newsel != self.current_extension:
1564 if self.current_extension:
1565 self.details_frame.config(text='')
1566 self.config_frame[self.current_extension].grid_forget()
1567 self.current_extension = None
1568 if newsel:
1569 self.details_frame.config(text=newsel)
1570 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1571 self.current_extension = newsel
1572
1573 def create_extension_frame(self, ext_name):
1574 """Create a frame holding the widgets to configure one extension"""
1575 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1576 self.config_frame[ext_name] = f
1577 entry_area = f.interior
csabella7eb58832017-07-04 21:30:58 -04001578 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001579 for row, opt in enumerate(self.extensions[ext_name]):
csabella7eb58832017-07-04 21:30:58 -04001580 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001581 label = Label(entry_area, text=opt['name'])
1582 label.grid(row=row, column=0, sticky=NW)
1583 var = opt['var']
1584 if opt['type'] == 'bool':
1585 Checkbutton(entry_area, textvariable=var, variable=var,
1586 onvalue='True', offvalue='False',
1587 indicatoron=FALSE, selectcolor='', width=8
1588 ).grid(row=row, column=1, sticky=W, padx=7)
1589 elif opt['type'] == 'int':
1590 Entry(entry_area, textvariable=var, validate='key',
1591 validatecommand=(self.is_int, '%P')
1592 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1593
1594 else:
1595 Entry(entry_area, textvariable=var
1596 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1597 return
1598
1599 def set_extension_value(self, section, opt):
csabella7eb58832017-07-04 21:30:58 -04001600 """Return True if the configuration was added or changed.
1601
1602 If the value is the same as the default, then remove it
1603 from user config file.
1604 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001605 name = opt['name']
1606 default = opt['default']
1607 value = opt['var'].get().strip() or default
1608 opt['var'].set(value)
1609 # if self.defaultCfg.has_section(section):
csabella7eb58832017-07-04 21:30:58 -04001610 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001611 if (value == default):
1612 return self.ext_userCfg.RemoveOption(section, name)
csabella7eb58832017-07-04 21:30:58 -04001613 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001614 return self.ext_userCfg.SetOption(section, name, value)
1615
1616 def save_all_changed_extensions(self):
csabella36329a42017-07-13 23:32:01 -04001617 """Save configuration changes to the user config file.
1618
1619 Attributes accessed:
1620 extensions
1621
1622 Methods:
1623 set_extension_value
1624 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001625 has_changes = False
1626 for ext_name in self.extensions:
1627 options = self.extensions[ext_name]
1628 for opt in options:
1629 if self.set_extension_value(ext_name, opt):
1630 has_changes = True
1631 if has_changes:
1632 self.ext_userCfg.Save()
1633
1634
csabella9397e2a2017-07-30 13:34:25 -04001635class FontPage(Frame):
1636
1637 def __init__(self, parent, highpage):
1638 super().__init__(parent)
1639 self.parent = parent
1640 self.highlight_sample = highpage.highlight_sample
1641 self.create_page_font_tab()
1642 self.load_font_cfg()
1643 self.load_tab_cfg()
1644
1645 def create_page_font_tab(self):
1646 """Return frame of widgets for Font/Tabs tab.
1647
1648 Fonts: Enable users to provisionally change font face, size, or
1649 boldness and to see the consequence of proposed choices. Each
1650 action set 3 options in changes structuree and changes the
1651 corresponding aspect of the font sample on this page and
1652 highlight sample on highlight page.
1653
1654 Function load_font_cfg initializes font vars and widgets from
1655 idleConf entries and tk.
1656
1657 Fontlist: mouse button 1 click or up or down key invoke
1658 on_fontlist_select(), which sets var font_name.
1659
1660 Sizelist: clicking the menubutton opens the dropdown menu. A
1661 mouse button 1 click or return key sets var font_size.
1662
1663 Bold_toggle: clicking the box toggles var font_bold.
1664
1665 Changing any of the font vars invokes var_changed_font, which
1666 adds all 3 font options to changes and calls set_samples.
1667 Set_samples applies a new font constructed from the font vars to
1668 font_sample and to highlight_sample on the hightlight page.
1669
1670 Tabs: Enable users to change spaces entered for indent tabs.
1671 Changing indent_scale value with the mouse sets Var space_num,
1672 which invokes the default callback to add an entry to
1673 changes. Load_tab_cfg initializes space_num to default.
1674
1675 Widget Structure: (*) widgets bound to self
1676 frame (of tab_pages)
1677 frame_font: LabelFrame
1678 frame_font_name: Frame
1679 font_name_title: Label
1680 (*)fontlist: ListBox - font_name
1681 scroll_font: Scrollbar
1682 frame_font_param: Frame
1683 font_size_title: Label
1684 (*)sizelist: DynOptionMenu - font_size
1685 (*)bold_toggle: Checkbutton - font_bold
1686 frame_font_sample: Frame
1687 (*)font_sample: Label
1688 frame_indent: LabelFrame
1689 indent_title: Label
1690 (*)indent_scale: Scale - space_num
1691 """
1692 parent = self.parent
1693 self.font_name = tracers.add(StringVar(parent), self.var_changed_font)
1694 self.font_size = tracers.add(StringVar(parent), self.var_changed_font)
1695 self.font_bold = tracers.add(BooleanVar(parent), self.var_changed_font)
1696 self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
1697
1698 # Create widgets:
1699 # body and body section frames.
1700 frame = self
1701 frame_font = LabelFrame(
1702 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
1703 frame_indent = LabelFrame(
1704 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
1705 # frame_font.
1706 frame_font_name = Frame(frame_font)
1707 frame_font_param = Frame(frame_font)
1708 font_name_title = Label(
1709 frame_font_name, justify=LEFT, text='Font Face :')
1710 self.fontlist = Listbox(frame_font_name, height=5,
1711 takefocus=True, exportselection=FALSE)
1712 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
1713 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
1714 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
1715 scroll_font = Scrollbar(frame_font_name)
1716 scroll_font.config(command=self.fontlist.yview)
1717 self.fontlist.config(yscrollcommand=scroll_font.set)
1718 font_size_title = Label(frame_font_param, text='Size :')
1719 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
1720 self.bold_toggle = Checkbutton(
1721 frame_font_param, variable=self.font_bold,
1722 onvalue=1, offvalue=0, text='Bold')
1723 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
1724 temp_font = tkFont.Font(parent, ('courier', 10, 'normal'))
1725 self.font_sample = Label(
1726 frame_font_sample, justify=LEFT, font=temp_font,
1727 text='AaBbCcDdEe\nFfGgHhIiJj\n1234567890\n#:+=(){}[]')
1728 # frame_indent.
1729 indent_title = Label(
1730 frame_indent, justify=LEFT,
1731 text='Python Standard: 4 Spaces!')
1732 self.indent_scale = Scale(
1733 frame_indent, variable=self.space_num,
1734 orient='horizontal', tickinterval=2, from_=2, to=16)
1735
1736 # Pack widgets:
1737 # body.
1738 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
1739 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
1740 # frame_font.
1741 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
1742 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
1743 font_name_title.pack(side=TOP, anchor=W)
1744 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
1745 scroll_font.pack(side=LEFT, fill=Y)
1746 font_size_title.pack(side=LEFT, anchor=W)
1747 self.sizelist.pack(side=LEFT, anchor=W)
1748 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
1749 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
1750 self.font_sample.pack(expand=TRUE, fill=BOTH)
1751 # frame_indent.
1752 frame_indent.pack(side=TOP, fill=X)
1753 indent_title.pack(side=TOP, anchor=W, padx=5)
1754 self.indent_scale.pack(side=TOP, padx=5, fill=X)
1755
1756 return frame
1757
1758 def load_font_cfg(self):
1759 """Load current configuration settings for the font options.
1760
1761 Retrieve current font with idleConf.GetFont and font families
1762 from tk. Setup fontlist and set font_name. Setup sizelist,
1763 which sets font_size. Set font_bold. Call set_samples.
1764 """
1765 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
1766 font_name = configured_font[0].lower()
1767 font_size = configured_font[1]
1768 font_bold = configured_font[2]=='bold'
1769
1770 # Set editor font selection list and font_name.
1771 fonts = list(tkFont.families(self))
1772 fonts.sort()
1773 for font in fonts:
1774 self.fontlist.insert(END, font)
1775 self.font_name.set(font_name)
1776 lc_fonts = [s.lower() for s in fonts]
1777 try:
1778 current_font_index = lc_fonts.index(font_name)
1779 self.fontlist.see(current_font_index)
1780 self.fontlist.select_set(current_font_index)
1781 self.fontlist.select_anchor(current_font_index)
1782 self.fontlist.activate(current_font_index)
1783 except ValueError:
1784 pass
1785 # Set font size dropdown.
1786 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
1787 '16', '18', '20', '22', '25', '29', '34', '40'),
1788 font_size)
1789 # Set font weight.
1790 self.font_bold.set(font_bold)
1791 self.set_samples()
1792
1793 def var_changed_font(self, *params):
1794 """Store changes to font attributes.
1795
1796 When one font attribute changes, save them all, as they are
1797 not independent from each other. In particular, when we are
1798 overriding the default font, we need to write out everything.
1799 """
1800 value = self.font_name.get()
1801 changes.add_option('main', 'EditorWindow', 'font', value)
1802 value = self.font_size.get()
1803 changes.add_option('main', 'EditorWindow', 'font-size', value)
1804 value = self.font_bold.get()
1805 changes.add_option('main', 'EditorWindow', 'font-bold', value)
1806 self.set_samples()
1807
1808 def on_fontlist_select(self, event):
1809 """Handle selecting a font from the list.
1810
1811 Event can result from either mouse click or Up or Down key.
1812 Set font_name and example displays to selection.
1813 """
1814 font = self.fontlist.get(
1815 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
1816 self.font_name.set(font.lower())
1817
1818 def set_samples(self, event=None):
1819 """Update update both screen samples with the font settings.
1820
1821 Called on font initialization and change events.
1822 Accesses font_name, font_size, and font_bold Variables.
1823 Updates font_sample and hightlight page highlight_sample.
1824 """
1825 font_name = self.font_name.get()
1826 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
1827 new_font = (font_name, self.font_size.get(), font_weight)
1828 self.font_sample['font'] = new_font
1829 self.highlight_sample['font'] = new_font
1830
1831 def load_tab_cfg(self):
1832 """Load current configuration settings for the tab options.
1833
1834 Attributes updated:
1835 space_num: Set to value from idleConf.
1836 """
1837 # Set indent sizes.
1838 space_num = idleConf.GetOption(
1839 'main', 'Indent', 'num-spaces', default=4, type='int')
1840 self.space_num.set(space_num)
1841
1842 def var_changed_space_num(self, *params):
1843 "Store change to indentation size."
1844 value = self.space_num.get()
1845 changes.add_option('main', 'Indent', 'num-spaces', value)
1846
1847
csabella45bf7232017-07-26 19:09:58 -04001848class VarTrace:
1849 """Maintain Tk variables trace state."""
1850
1851 def __init__(self):
1852 """Store Tk variables and callbacks.
1853
1854 untraced: List of tuples (var, callback)
1855 that do not have the callback attached
1856 to the Tk var.
1857 traced: List of tuples (var, callback) where
1858 that callback has been attached to the var.
1859 """
1860 self.untraced = []
1861 self.traced = []
1862
Terry Jan Reedy5d0f30a2017-07-28 17:00:02 -04001863 def clear(self):
1864 "Clear lists (for tests)."
1865 self.untraced.clear()
1866 self.traced.clear()
1867
csabella45bf7232017-07-26 19:09:58 -04001868 def add(self, var, callback):
1869 """Add (var, callback) tuple to untraced list.
1870
1871 Args:
1872 var: Tk variable instance.
csabella5b591542017-07-28 14:40:59 -04001873 callback: Either function name to be used as a callback
1874 or a tuple with IdleConf config-type, section, and
1875 option names used in the default callback.
csabella45bf7232017-07-26 19:09:58 -04001876
1877 Return:
1878 Tk variable instance.
1879 """
1880 if isinstance(callback, tuple):
1881 callback = self.make_callback(var, callback)
1882 self.untraced.append((var, callback))
1883 return var
1884
1885 @staticmethod
1886 def make_callback(var, config):
1887 "Return default callback function to add values to changes instance."
1888 def default_callback(*params):
1889 "Add config values to changes instance."
1890 changes.add_option(*config, var.get())
1891 return default_callback
1892
1893 def attach(self):
1894 "Attach callback to all vars that are not traced."
1895 while self.untraced:
1896 var, callback = self.untraced.pop()
1897 var.trace_add('write', callback)
1898 self.traced.append((var, callback))
1899
1900 def detach(self):
1901 "Remove callback from traced vars."
1902 while self.traced:
1903 var, callback = self.traced.pop()
1904 var.trace_remove('write', var.trace_info()[0][1])
1905 self.untraced.append((var, callback))
1906
1907
csabella5b591542017-07-28 14:40:59 -04001908tracers = VarTrace()
1909
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001910help_common = '''\
1911When you click either the Apply or Ok buttons, settings in this
1912dialog that are different from IDLE's default are saved in
1913a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001914these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001915machine. Some do not take affect until IDLE is restarted.
1916[Cancel] only cancels changes made since the last save.
1917'''
1918help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001919 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001920Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001921The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001922be used with older IDLE releases if it is saved as a custom
1923theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001924''',
1925 'Keys': '''
1926Keys:
1927The IDLE Modern Unix key set is new in June 2016. It can only
1928be used with older IDLE releases if it is saved as a custom
1929key set, with a different name.
1930''',
wohlgangerfae2c352017-06-27 21:36:23 -05001931 'Extensions': '''
1932Extensions:
1933
1934Autocomplete: Popupwait is milleseconds to wait after key char, without
1935cursor movement, before popping up completion box. Key char is '.' after
1936identifier or a '/' (or '\\' on Windows) within a string.
1937
1938FormatParagraph: Max-width is max chars in lines after re-formatting.
1939Use with paragraphs in both strings and comment blocks.
1940
1941ParenMatch: Style indicates what is highlighted when closer is entered:
1942'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1943'expression' (default) - also everything in between. Flash-delay is how
1944long to highlight if cursor is not moved (0 means forever).
1945'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001946}
1947
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001948
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001949def is_int(s):
1950 "Return 's is blank or represents an int'"
1951 if not s:
1952 return True
1953 try:
1954 int(s)
1955 return True
1956 except ValueError:
1957 return False
1958
1959
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001960class VerticalScrolledFrame(Frame):
1961 """A pure Tkinter vertically scrollable frame.
1962
1963 * Use the 'interior' attribute to place widgets inside the scrollable frame
1964 * Construct and pack/place/grid normally
1965 * This frame only allows vertical scrolling
1966 """
1967 def __init__(self, parent, *args, **kw):
1968 Frame.__init__(self, parent, *args, **kw)
1969
csabella7eb58832017-07-04 21:30:58 -04001970 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001971 vscrollbar = Scrollbar(self, orient=VERTICAL)
1972 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1973 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001974 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001975 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1976 vscrollbar.config(command=canvas.yview)
1977
csabella7eb58832017-07-04 21:30:58 -04001978 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001979 canvas.xview_moveto(0)
1980 canvas.yview_moveto(0)
1981
csabella7eb58832017-07-04 21:30:58 -04001982 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001983 self.interior = interior = Frame(canvas)
1984 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1985
csabella7eb58832017-07-04 21:30:58 -04001986 # Track changes to the canvas and frame width and sync them,
1987 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001988 def _configure_interior(event):
csabella7eb58832017-07-04 21:30:58 -04001989 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001990 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1991 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001992 interior.bind('<Configure>', _configure_interior)
1993
1994 def _configure_canvas(event):
1995 if interior.winfo_reqwidth() != canvas.winfo_width():
csabella7eb58832017-07-04 21:30:58 -04001996 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001997 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1998 canvas.bind('<Configure>', _configure_canvas)
1999
2000 return
2001
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04002002
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00002003if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04002004 import unittest
2005 unittest.main('idlelib.idle_test.test_configdialog',
2006 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04002007 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04002008 run(ConfigDialog)