blob: 84092275183685ac9067489de40b116b523884be [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,
17 HORIZONTAL, VERTICAL, ANCHOR, END)
Terry Jan Reedy01e35752016-06-10 18:19:21 -040018from tkinter.ttk import Scrollbar
Georg Brandl14fc4272008-05-17 18:39:55 +000019import tkinter.colorchooser as tkColorChooser
20import tkinter.font as tkFont
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040021import tkinter.messagebox as tkMessageBox
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000022
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040023from idlelib.config import idleConf
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
terryjreedye5bb1122017-07-05 00:54:55 -040031
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000032class ConfigDialog(Toplevel):
terryjreedye5bb1122017-07-05 00:54:55 -040033 """Config dialog for IDLE.
34 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000035
Terry Jan Reedycd567362014-10-17 01:31:35 -040036 def __init__(self, parent, title='', _htest=False, _utest=False):
terryjreedye5bb1122017-07-05 00:54:55 -040037 """Show the tabbed dialog for user configuration.
38
39 parent - parent of this dialog
40 title - string which is the title of this popup dialog
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040041 _htest - bool, change box location when running htest
Terry Jan Reedycfa89502014-07-14 23:07:32 -040042 _utest - bool, don't wait_window when running unittest
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040043 """
Steven M. Gavad721c482001-07-31 10:46:53 +000044 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040045 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040046 if _htest:
47 parent.instance_dict = {}
terryjreedy938e7382017-06-26 20:48:39 -040048 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000049
Steven M. Gavad721c482001-07-31 10:46:53 +000050 self.configure(borderwidth=5)
Terry Jan Reedycd567362014-10-17 01:31:35 -040051 self.title(title or 'IDLE Preferences')
terryjreedy938e7382017-06-26 20:48:39 -040052 x = parent.winfo_rootx() + 20
53 y = parent.winfo_rooty() + (30 if not _htest else 150)
54 self.geometry(f'+{x}+{y}')
terryjreedye5bb1122017-07-05 00:54:55 -040055 # Each theme element key is its display name.
56 # The first value of the tuple is the sample area tag name.
57 # The second value is the display name list sort index.
terryjreedy938e7382017-06-26 20:48:39 -040058 self.theme_elements={
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -040059 'Normal Text': ('normal', '00'),
60 'Python Keywords': ('keyword', '01'),
61 'Python Definitions': ('definition', '02'),
62 'Python Builtins': ('builtin', '03'),
63 'Python Comments': ('comment', '04'),
64 'Python Strings': ('string', '05'),
65 'Selected Text': ('hilite', '06'),
66 'Found Text': ('hit', '07'),
67 'Cursor': ('cursor', '08'),
68 'Editor Breakpoint': ('break', '09'),
69 'Shell Normal Text': ('console', '10'),
70 'Shell Error Text': ('error', '11'),
71 'Shell Stdout Text': ('stdout', '12'),
72 'Shell Stderr Text': ('stderr', '13'),
Kurt B. Kaiser73360a32004-03-08 18:15:31 +000073 }
terryjreedye5bb1122017-07-05 00:54:55 -040074 self.reset_changed_items() # Initialize changed_items dict.
terryjreedy938e7382017-06-26 20:48:39 -040075 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040076 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000077 self.transient(parent)
78 self.grab_set()
terryjreedy938e7382017-06-26 20:48:39 -040079 self.protocol("WM_DELETE_WINDOW", self.cancel)
80 self.tab_pages.focus_set()
terryjreedye5bb1122017-07-05 00:54:55 -040081 # XXX Decide whether to keep or delete these key bindings.
82 # Key bindings for this dialog.
83 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
84 # self.bind('<Alt-a>', self.Apply) #apply changes, save
85 # self.bind('<F1>', self.Help) #context help
terryjreedy938e7382017-06-26 20:48:39 -040086 self.load_configs()
terryjreedye5bb1122017-07-05 00:54:55 -040087 self.attach_var_callbacks() # Avoid callbacks during load_configs.
Guido van Rossum8ce8a782007-11-01 19:42:39 +000088
Terry Jan Reedycfa89502014-07-14 23:07:32 -040089 if not _utest:
90 self.wm_deiconify()
91 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000092
terryjreedy938e7382017-06-26 20:48:39 -040093 def create_widgets(self):
terryjreedye5bb1122017-07-05 00:54:55 -040094 "Create and place widgets for tabbed dialog."
terryjreedy938e7382017-06-26 20:48:39 -040095 self.tab_pages = TabbedPageSet(self,
Terry Jan Reedy93f35422015-10-13 22:03:51 -040096 page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
97 'Extensions'])
terryjreedy938e7382017-06-26 20:48:39 -040098 self.tab_pages.pack(side=TOP, expand=TRUE, fill=BOTH)
99 self.create_page_font_tab()
100 self.create_page_highlight()
101 self.create_page_keys()
102 self.create_page_general()
103 self.create_page_extensions()
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400104 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400105
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400106 def create_action_buttons(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400107 "Return frame of action buttons for dialog."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400108 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400109 # Changing the default padding on OSX results in unreadable
terryjreedye5bb1122017-07-05 00:54:55 -0400110 # text in the buttons.
terryjreedy938e7382017-06-26 20:48:39 -0400111 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000112 else:
terryjreedy938e7382017-06-26 20:48:39 -0400113 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400114 outer = Frame(self, pady=2)
115 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400116 for txt, cmd in (
terryjreedy938e7382017-06-26 20:48:39 -0400117 ('Ok', self.ok),
118 ('Apply', self.apply),
119 ('Cancel', self.cancel),
120 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400121 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
terryjreedy938e7382017-06-26 20:48:39 -0400122 **padding_args).pack(side=LEFT, padx=5)
terryjreedye5bb1122017-07-05 00:54:55 -0400123 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400124 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
125 buttons.pack(side=BOTTOM)
126 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400127
terryjreedy938e7382017-06-26 20:48:39 -0400128 def create_page_font_tab(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400129 """Return frame of widgets for Font/Tabs tab.
130
131 Configuration attributes:
132 font_size: Font size.
133 font_bold: Select font bold or not.
134 font_name: Font face.
135 space_num: Indentation width.
136 edit_font: Font widget with default font name, size, and weight.
137 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400138 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400139 self.font_size = StringVar(parent)
140 self.font_bold = BooleanVar(parent)
141 self.font_name = StringVar(parent)
142 self.space_num = IntVar(parent)
143 self.edit_font = tkFont.Font(parent, ('courier', 10, 'normal'))
Terry Jan Reedy22405332014-07-30 19:24:32 -0400144
Steven M. Gavaf213ccb2001-08-05 08:00:28 +0000145 ##widget creation
146 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400147 frame = self.tab_pages.pages['Fonts/Tabs'].frame
Steven M. Gavaf213ccb2001-08-05 08:00:28 +0000148 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400149 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400150 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
terryjreedy938e7382017-06-26 20:48:39 -0400151 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400152 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
terryjreedy938e7382017-06-26 20:48:39 -0400153 #frame_font
154 frame_font_name = Frame(frame_font)
155 frame_font_param = Frame(frame_font)
156 font_name_title = Label(
157 frame_font_name, justify=LEFT, text='Font Face :')
158 self.list_fonts = Listbox(
159 frame_font_name, height=5, takefocus=FALSE, exportselection=FALSE)
160 self.list_fonts.bind(
161 '<ButtonRelease-1>', self.on_list_fonts_button_release)
162 scroll_font = Scrollbar(frame_font_name)
163 scroll_font.config(command=self.list_fonts.yview)
164 self.list_fonts.config(yscrollcommand=scroll_font.set)
165 font_size_title = Label(frame_font_param, text='Size :')
166 self.opt_menu_font_size = DynOptionMenu(
167 frame_font_param, self.font_size, None, command=self.set_font_sample)
168 check_font_bold = Checkbutton(
169 frame_font_param, variable=self.font_bold, onvalue=1,
170 offvalue=0, text='Bold', command=self.set_font_sample)
171 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
172 self.font_sample = Label(
173 frame_font_sample, justify=LEFT, font=self.edit_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400174 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
terryjreedy938e7382017-06-26 20:48:39 -0400175 #frame_indent
176 frame_indent_size = Frame(frame_indent)
177 indent_size_title = Label(
178 frame_indent_size, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400179 text='Python Standard: 4 Spaces!')
terryjreedy938e7382017-06-26 20:48:39 -0400180 self.scale_indent_size = Scale(
181 frame_indent_size, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400182 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400183
Steven M. Gavaf213ccb2001-08-05 08:00:28 +0000184 #widget packing
185 #body
terryjreedy938e7382017-06-26 20:48:39 -0400186 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
187 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
188 #frame_font
189 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
190 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
191 font_name_title.pack(side=TOP, anchor=W)
192 self.list_fonts.pack(side=LEFT, expand=TRUE, fill=X)
193 scroll_font.pack(side=LEFT, fill=Y)
194 font_size_title.pack(side=LEFT, anchor=W)
195 self.opt_menu_font_size.pack(side=LEFT, anchor=W)
196 check_font_bold.pack(side=LEFT, anchor=W, padx=20)
197 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
198 self.font_sample.pack(expand=TRUE, fill=BOTH)
199 #frame_indent
200 frame_indent_size.pack(side=TOP, fill=X)
201 indent_size_title.pack(side=TOP, anchor=W, padx=5)
202 self.scale_indent_size.pack(side=TOP, padx=5, fill=X)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000203 return frame
204
terryjreedy938e7382017-06-26 20:48:39 -0400205 def create_page_highlight(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400206 """Return frame of widgets for Highlighting tab.
207
208 Configuration attributes:
209 builtin_theme: Menu variable for built-in theme.
210 custom_theme: Menu variable for custom theme.
211 fg_bg_toggle: Toggle for foreground/background color.
212 colour: Color of selected target.
213 is_builtin_theme: Selector for built-in or custom theme.
214 highlight_target: Menu variable for the highlight tag target.
215 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400216 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400217 self.builtin_theme = StringVar(parent)
218 self.custom_theme = StringVar(parent)
219 self.fg_bg_toggle = BooleanVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400220 self.colour = StringVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400221 # XXX - font_name is defined in create_page_font_tab. Needed here too?
terryjreedy938e7382017-06-26 20:48:39 -0400222 self.font_name = StringVar(parent)
223 self.is_builtin_theme = BooleanVar(parent)
224 self.highlight_target = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400225
Steven M. Gava952d0a52001-08-03 04:43:44 +0000226 ##widget creation
227 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400228 frame = self.tab_pages.pages['Highlighting'].frame
Steven M. Gava952d0a52001-08-03 04:43:44 +0000229 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400230 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400231 text=' Custom Highlighting ')
terryjreedy938e7382017-06-26 20:48:39 -0400232 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400233 text=' Highlighting Theme ')
terryjreedy938e7382017-06-26 20:48:39 -0400234 #frame_custom
235 self.text_highlight_sample=Text(
236 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400237 font=('courier', 12, ''), cursor='hand2', width=21, height=11,
238 takefocus=FALSE, highlightthickness=0, wrap=NONE)
terryjreedy938e7382017-06-26 20:48:39 -0400239 text=self.text_highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400240 text.bind('<Double-Button-1>', lambda e: 'break')
241 text.bind('<B1-Motion>', lambda e: 'break')
terryjreedy938e7382017-06-26 20:48:39 -0400242 text_and_tags=(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400243 ('#you can click here', 'comment'), ('\n', 'normal'),
244 ('#to choose items', 'comment'), ('\n', 'normal'),
245 ('def', 'keyword'), (' ', 'normal'),
246 ('func', 'definition'), ('(param):\n ', 'normal'),
247 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
248 ("'string'", 'string'), ('\n var1 = ', 'normal'),
249 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
250 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
251 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400252 ('None', 'keyword'), (')\n', 'normal'),
253 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400254 (' error ', 'error'), (' ', 'normal'),
255 ('cursor |', 'cursor'), ('\n ', 'normal'),
256 ('shell', 'console'), (' ', 'normal'),
257 ('stdout', 'stdout'), (' ', 'normal'),
258 ('stderr', 'stderr'), ('\n', 'normal'))
terryjreedy938e7382017-06-26 20:48:39 -0400259 for texttag in text_and_tags:
260 text.insert(END, texttag[0], texttag[1])
261 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400262 def tem(event, elem=element):
terryjreedy938e7382017-06-26 20:48:39 -0400263 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400264 text.tag_bind(
terryjreedy938e7382017-06-26 20:48:39 -0400265 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Steven M. Gavae16d94b2001-11-03 05:07:28 +0000266 text.config(state=DISABLED)
terryjreedy938e7382017-06-26 20:48:39 -0400267 self.frame_colour_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
268 frame_fg_bg_toggle = Frame(frame_custom)
269 button_set_colour = Button(
270 self.frame_colour_set, text='Choose Colour for :',
271 command=self.get_colour, highlightthickness=0)
272 self.opt_menu_highlight_target = DynOptionMenu(
273 self.frame_colour_set, self.highlight_target, None,
274 highlightthickness=0) #, command=self.set_highlight_targetBinding
275 self.radio_fg = Radiobutton(
276 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
277 text='Foreground', command=self.set_colour_sample_binding)
278 self.radio_bg=Radiobutton(
279 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
280 text='Background', command=self.set_colour_sample_binding)
281 self.fg_bg_toggle.set(1)
282 button_save_custom_theme = Button(
283 frame_custom, text='Save as New Custom Theme',
284 command=self.save_as_new_theme)
285 #frame_theme
286 theme_type_title = Label(frame_theme, text='Select : ')
287 self.radio_theme_builtin = Radiobutton(
288 frame_theme, variable=self.is_builtin_theme, value=1,
289 command=self.set_theme_type, text='a Built-in Theme')
290 self.radio_theme_custom = Radiobutton(
291 frame_theme, variable=self.is_builtin_theme, value=0,
292 command=self.set_theme_type, text='a Custom Theme')
293 self.opt_menu_theme_builtin = DynOptionMenu(
294 frame_theme, self.builtin_theme, None, command=None)
295 self.opt_menu_theme_custom=DynOptionMenu(
296 frame_theme, self.custom_theme, None, command=None)
297 self.button_delete_custom_theme=Button(
298 frame_theme, text='Delete Custom Theme',
299 command=self.delete_custom_theme)
300 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400301
Steven M. Gava952d0a52001-08-03 04:43:44 +0000302 ##widget packing
303 #body
terryjreedy938e7382017-06-26 20:48:39 -0400304 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
305 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
306 #frame_custom
307 self.frame_colour_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
308 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
309 self.text_highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400310 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
terryjreedy938e7382017-06-26 20:48:39 -0400311 button_set_colour.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
312 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400313 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
terryjreedy938e7382017-06-26 20:48:39 -0400314 self.radio_fg.pack(side=LEFT, anchor=E)
315 self.radio_bg.pack(side=RIGHT, anchor=W)
316 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
317 #frame_theme
318 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
319 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
320 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
321 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
322 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
323 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500324 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000325 return frame
326
terryjreedy938e7382017-06-26 20:48:39 -0400327 def create_page_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400328 """Return frame of widgets for Keys tab.
329
330 Configuration attributes:
331 builtin_keys: Menu variable for built-in keybindings.
332 custom_keys: Menu variable for custom keybindings.
333 are_keys_builtin: Selector for built-in or custom keybindings.
334 keybinding: Action/key bindings.
335 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400336 parent = self.parent
terryjreedye5bb1122017-07-05 00:54:55 -0400337 # XXX - binding_target isn't used.
terryjreedy938e7382017-06-26 20:48:39 -0400338 self.binding_target = StringVar(parent)
339 self.builtin_keys = StringVar(parent)
340 self.custom_keys = StringVar(parent)
341 self.are_keys_builtin = BooleanVar(parent)
342 self.keybinding = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400343
Steven M. Gava60fc7072001-08-04 13:58:22 +0000344 ##widget creation
345 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400346 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +0000347 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400348 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400349 frame, borderwidth=2, relief=GROOVE,
350 text=' Custom Key Bindings ')
terryjreedy938e7382017-06-26 20:48:39 -0400351 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400352 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
terryjreedy938e7382017-06-26 20:48:39 -0400353 #frame_custom
354 frame_target = Frame(frame_custom)
355 target_title = Label(frame_target, text='Action - Key(s)')
356 scroll_target_y = Scrollbar(frame_target)
357 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
358 self.list_bindings = Listbox(
359 frame_target, takefocus=FALSE, exportselection=FALSE)
360 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
361 scroll_target_y.config(command=self.list_bindings.yview)
362 scroll_target_x.config(command=self.list_bindings.xview)
363 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
364 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
365 self.button_new_keys = Button(
366 frame_custom, text='Get New Keys for Selection',
367 command=self.get_new_keys, state=DISABLED)
368 #frame_key_sets
369 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +0000370 for i in range(2)]
terryjreedy938e7382017-06-26 20:48:39 -0400371 self.radio_keys_builtin = Radiobutton(
372 frames[0], variable=self.are_keys_builtin, value=1,
373 command=self.set_keys_type, text='Use a Built-in Key Set')
374 self.radio_keys_custom = Radiobutton(
375 frames[0], variable=self.are_keys_builtin, value=0,
376 command=self.set_keys_type, text='Use a Custom Key Set')
377 self.opt_menu_keys_builtin = DynOptionMenu(
378 frames[0], self.builtin_keys, None, command=None)
379 self.opt_menu_keys_custom = DynOptionMenu(
380 frames[0], self.custom_keys, None, command=None)
381 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400382 frames[1], text='Delete Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400383 command=self.delete_custom_keys)
384 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400385 frames[1], text='Save as New Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400386 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400387 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400388
Steven M. Gava60fc7072001-08-04 13:58:22 +0000389 ##widget packing
390 #body
terryjreedy938e7382017-06-26 20:48:39 -0400391 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
392 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
393 #frame_custom
394 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
395 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +0000396 #frame target
terryjreedy938e7382017-06-26 20:48:39 -0400397 frame_target.columnconfigure(0, weight=1)
398 frame_target.rowconfigure(1, weight=1)
399 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
400 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
401 scroll_target_y.grid(row=1, column=1, sticky=NS)
402 scroll_target_x.grid(row=2, column=0, sticky=EW)
403 #frame_key_sets
404 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
405 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
406 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
407 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400408 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
terryjreedy938e7382017-06-26 20:48:39 -0400409 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
410 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +0000411 frames[0].pack(side=TOP, fill=BOTH, expand=True)
412 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000413 return frame
414
terryjreedy938e7382017-06-26 20:48:39 -0400415 def create_page_general(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400416 """Return frame of widgets for General tab.
417
418 Configuration attributes:
419 win_width: Initial window width in characters.
420 win_height: Initial window height in characters.
421 startup_edit: Selector for opening in editor or shell mode.
422 autosave: Selector for save prompt popup when using Run.
423 encoding: ?
424 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400425 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400426 self.win_width = StringVar(parent)
427 self.win_height = StringVar(parent)
428 self.startup_edit = IntVar(parent)
429 self.autosave = IntVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400430 # XXX - encoding isn't on the screen to be set, but is saved to config.
Terry Jan Reedy22405332014-07-30 19:24:32 -0400431 self.encoding = StringVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400432 # XXX - user_help_browser and help_browser aren't used.
terryjreedy938e7382017-06-26 20:48:39 -0400433 self.user_help_browser = BooleanVar(parent)
434 self.help_browser = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400435
Steven M. Gava230e5782001-08-07 03:28:25 +0000436 #widget creation
437 #body
terryjreedy938e7382017-06-26 20:48:39 -0400438 frame = self.tab_pages.pages['General'].frame
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000439 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400440 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400441 text=' Startup Preferences ')
terryjreedy938e7382017-06-26 20:48:39 -0400442 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
443 text=' autosave Preferences ')
444 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
445 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400446 text=' Additional Help Sources ')
terryjreedy938e7382017-06-26 20:48:39 -0400447 #frame_run
448 startup_title = Label(frame_run, text='At Startup')
449 self.radio_startup_edit = Radiobutton(
450 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500451 text="Open Edit Window")
terryjreedy938e7382017-06-26 20:48:39 -0400452 self.radio_startup_shell = Radiobutton(
453 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500454 text='Open Shell Window')
terryjreedy938e7382017-06-26 20:48:39 -0400455 #frame_save
456 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
457 self.radio_save_ask = Radiobutton(
458 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500459 text="Prompt to Save")
terryjreedy938e7382017-06-26 20:48:39 -0400460 self.radio_save_auto = Radiobutton(
461 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500462 text='No Prompt')
terryjreedy938e7382017-06-26 20:48:39 -0400463 #frame_win_size
464 win_size_title = Label(
465 frame_win_size, text='Initial Window Size (in characters)')
466 win_width_title = Label(frame_win_size, text='Width')
467 self.entry_win_width = Entry(
468 frame_win_size, textvariable=self.win_width, width=3)
469 win_height_title = Label(frame_win_size, text='Height')
470 self.entry_win_height = Entry(
471 frame_win_size, textvariable=self.win_height, width=3)
472 #frame_help
473 frame_helplist = Frame(frame_help)
474 frame_helplist_buttons = Frame(frame_helplist)
475 scroll_helplist = Scrollbar(frame_helplist)
476 self.list_help = Listbox(
477 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000478 exportselection=FALSE)
terryjreedy938e7382017-06-26 20:48:39 -0400479 scroll_helplist.config(command=self.list_help.yview)
480 self.list_help.config(yscrollcommand=scroll_helplist.set)
481 self.list_help.bind('<ButtonRelease-1>', self.help_source_selected)
482 self.button_helplist_edit = Button(
483 frame_helplist_buttons, text='Edit', state=DISABLED,
484 width=8, command=self.helplist_item_edit)
485 self.button_helplist_add = Button(
486 frame_helplist_buttons, text='Add',
487 width=8, command=self.helplist_item_add)
488 self.button_helplist_remove = Button(
489 frame_helplist_buttons, text='Remove', state=DISABLED,
490 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400491
Steven M. Gava230e5782001-08-07 03:28:25 +0000492 #widget packing
493 #body
terryjreedy938e7382017-06-26 20:48:39 -0400494 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
495 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
496 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
497 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
498 #frame_run
499 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
500 self.radio_startup_shell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
501 self.radio_startup_edit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
502 #frame_save
503 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
504 self.radio_save_auto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
505 self.radio_save_ask.pack(side=RIGHT, anchor=W, padx=5, pady=5)
506 #frame_win_size
507 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
508 self.entry_win_height.pack(side=RIGHT, anchor=E, padx=10, pady=5)
509 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
510 self.entry_win_width.pack(side=RIGHT, anchor=E, padx=10, pady=5)
511 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
512 #frame_help
513 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
514 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
515 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
516 self.list_help.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
517 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
518 self.button_helplist_add.pack(side=TOP, anchor=W)
519 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000520 return frame
521
terryjreedy938e7382017-06-26 20:48:39 -0400522 def attach_var_callbacks(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400523 "Attach callbacks to variables that can be changed."
terryjreedy938e7382017-06-26 20:48:39 -0400524 self.font_size.trace_add('write', self.var_changed_font)
525 self.font_name.trace_add('write', self.var_changed_font)
526 self.font_bold.trace_add('write', self.var_changed_font)
527 self.space_num.trace_add('write', self.var_changed_space_num)
528 self.colour.trace_add('write', self.var_changed_colour)
529 self.builtin_theme.trace_add('write', self.var_changed_builtin_theme)
530 self.custom_theme.trace_add('write', self.var_changed_custom_theme)
531 self.is_builtin_theme.trace_add('write', self.var_changed_is_builtin_theme)
532 self.highlight_target.trace_add('write', self.var_changed_highlight_target)
533 self.keybinding.trace_add('write', self.var_changed_keybinding)
534 self.builtin_keys.trace_add('write', self.var_changed_builtin_keys)
535 self.custom_keys.trace_add('write', self.var_changed_custom_keys)
536 self.are_keys_builtin.trace_add('write', self.var_changed_are_keys_builtin)
537 self.win_width.trace_add('write', self.var_changed_win_width)
538 self.win_height.trace_add('write', self.var_changed_win_height)
539 self.startup_edit.trace_add('write', self.var_changed_startup_edit)
540 self.autosave.trace_add('write', self.var_changed_autosave)
541 self.encoding.trace_add('write', self.var_changed_encoding)
Steven M. Gava052937f2002-02-11 02:20:53 +0000542
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400543 def remove_var_callbacks(self):
544 "Remove callbacks to prevent memory leaks."
545 for var in (
terryjreedy938e7382017-06-26 20:48:39 -0400546 self.font_size, self.font_name, self.font_bold,
547 self.space_num, self.colour, self.builtin_theme,
548 self.custom_theme, self.is_builtin_theme, self.highlight_target,
549 self.keybinding, self.builtin_keys, self.custom_keys,
550 self.are_keys_builtin, self.win_width, self.win_height,
551 self.startup_edit, self.autosave, self.encoding,):
Serhiy Storchaka81221742016-06-26 09:46:57 +0300552 var.trace_remove('write', var.trace_info()[0][1])
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400553
terryjreedy938e7382017-06-26 20:48:39 -0400554 def var_changed_font(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400555 """Store changes to font attributes.
556
557 When one font attribute changes, save them all, as they are
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400558 not independent from each other. In particular, when we are
559 overriding the default font, we need to write out everything.
terryjreedye5bb1122017-07-05 00:54:55 -0400560 """
terryjreedy938e7382017-06-26 20:48:39 -0400561 value = self.font_name.get()
562 self.add_changed_item('main', 'EditorWindow', 'font', value)
563 value = self.font_size.get()
564 self.add_changed_item('main', 'EditorWindow', 'font-size', value)
565 value = self.font_bold.get()
566 self.add_changed_item('main', 'EditorWindow', 'font-bold', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000567
terryjreedy938e7382017-06-26 20:48:39 -0400568 def var_changed_space_num(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400569 "Store change to indentation size."
terryjreedy938e7382017-06-26 20:48:39 -0400570 value = self.space_num.get()
571 self.add_changed_item('main', 'Indent', 'num-spaces', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000572
terryjreedy938e7382017-06-26 20:48:39 -0400573 def var_changed_colour(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400574 "Process change to color choice."
terryjreedy938e7382017-06-26 20:48:39 -0400575 self.on_new_colour_set()
Steven M. Gava052937f2002-02-11 02:20:53 +0000576
terryjreedy938e7382017-06-26 20:48:39 -0400577 def var_changed_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400578 """Process new builtin theme selection.
579
580 Add the changed theme's name to the changed_items and recreate
581 the sample with the values from the selected theme.
582 """
terryjreedy938e7382017-06-26 20:48:39 -0400583 old_themes = ('IDLE Classic', 'IDLE New')
584 value = self.builtin_theme.get()
585 if value not in old_themes:
586 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
587 self.add_changed_item('main', 'Theme', 'name', old_themes[0])
588 self.add_changed_item('main', 'Theme', 'name2', value)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500589 self.new_custom_theme.config(text='New theme, see Help',
590 fg='#500000')
591 else:
terryjreedy938e7382017-06-26 20:48:39 -0400592 self.add_changed_item('main', 'Theme', 'name', value)
593 self.add_changed_item('main', 'Theme', 'name2', '')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500594 self.new_custom_theme.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400595 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000596
terryjreedy938e7382017-06-26 20:48:39 -0400597 def var_changed_custom_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400598 """Process new custom theme selection.
599
600 If a new custom theme is selected, add the name to the
601 changed_items and apply the theme to the sample.
602 """
terryjreedy938e7382017-06-26 20:48:39 -0400603 value = self.custom_theme.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000604 if value != '- no custom themes -':
terryjreedy938e7382017-06-26 20:48:39 -0400605 self.add_changed_item('main', 'Theme', 'name', value)
606 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000607
terryjreedy938e7382017-06-26 20:48:39 -0400608 def var_changed_is_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400609 """Process toggle between builtin and custom theme.
610
611 Update the default toggle value and apply the newly
612 selected theme type.
613 """
terryjreedy938e7382017-06-26 20:48:39 -0400614 value = self.is_builtin_theme.get()
615 self.add_changed_item('main', 'Theme', 'default', value)
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000616 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400617 self.var_changed_builtin_theme()
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000618 else:
terryjreedy938e7382017-06-26 20:48:39 -0400619 self.var_changed_custom_theme()
Steven M. Gava052937f2002-02-11 02:20:53 +0000620
terryjreedy938e7382017-06-26 20:48:39 -0400621 def var_changed_highlight_target(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400622 "Process selection of new target tag for highlighting."
terryjreedy938e7382017-06-26 20:48:39 -0400623 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000624
terryjreedy938e7382017-06-26 20:48:39 -0400625 def var_changed_keybinding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400626 "Store change to a keybinding."
terryjreedy938e7382017-06-26 20:48:39 -0400627 value = self.keybinding.get()
628 key_set = self.custom_keys.get()
629 event = self.list_bindings.get(ANCHOR).split()[0]
Steven M. Gavaa498af22002-02-01 01:33:36 +0000630 if idleConf.IsCoreBinding(event):
terryjreedy938e7382017-06-26 20:48:39 -0400631 self.add_changed_item('keys', key_set, event, value)
terryjreedye5bb1122017-07-05 00:54:55 -0400632 else: # Event is an extension binding.
terryjreedy938e7382017-06-26 20:48:39 -0400633 ext_name = idleConf.GetExtnNameForEvent(event)
634 ext_keybind_section = ext_name + '_cfgBindings'
635 self.add_changed_item('extensions', ext_keybind_section, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000636
terryjreedy938e7382017-06-26 20:48:39 -0400637 def var_changed_builtin_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400638 "Process selection of builtin key set."
terryjreedy938e7382017-06-26 20:48:39 -0400639 old_keys = (
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400640 'IDLE Classic Windows',
641 'IDLE Classic Unix',
642 'IDLE Classic Mac',
643 'IDLE Classic OSX',
644 )
terryjreedy938e7382017-06-26 20:48:39 -0400645 value = self.builtin_keys.get()
646 if value not in old_keys:
647 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
648 self.add_changed_item('main', 'Keys', 'name', old_keys[0])
649 self.add_changed_item('main', 'Keys', 'name2', value)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400650 self.new_custom_keys.config(text='New key set, see Help',
651 fg='#500000')
652 else:
terryjreedy938e7382017-06-26 20:48:39 -0400653 self.add_changed_item('main', 'Keys', 'name', value)
654 self.add_changed_item('main', 'Keys', 'name2', '')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400655 self.new_custom_keys.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400656 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000657
terryjreedy938e7382017-06-26 20:48:39 -0400658 def var_changed_custom_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400659 "Process selection of custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400660 value = self.custom_keys.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000661 if value != '- no custom keys -':
terryjreedy938e7382017-06-26 20:48:39 -0400662 self.add_changed_item('main', 'Keys', 'name', value)
663 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000664
terryjreedy938e7382017-06-26 20:48:39 -0400665 def var_changed_are_keys_builtin(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400666 "Process toggle between builtin key set and custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400667 value = self.are_keys_builtin.get()
668 self.add_changed_item('main', 'Keys', 'default', value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000669 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400670 self.var_changed_builtin_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000671 else:
terryjreedy938e7382017-06-26 20:48:39 -0400672 self.var_changed_custom_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000673
terryjreedy938e7382017-06-26 20:48:39 -0400674 def var_changed_win_width(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400675 "Store change to window width."
terryjreedy938e7382017-06-26 20:48:39 -0400676 value = self.win_width.get()
677 self.add_changed_item('main', 'EditorWindow', 'width', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000678
terryjreedy938e7382017-06-26 20:48:39 -0400679 def var_changed_win_height(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400680 "Store change to window height."
terryjreedy938e7382017-06-26 20:48:39 -0400681 value = self.win_height.get()
682 self.add_changed_item('main', 'EditorWindow', 'height', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000683
terryjreedy938e7382017-06-26 20:48:39 -0400684 def var_changed_startup_edit(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400685 "Store change to toggle for starting IDLE in the editor or shell."
terryjreedy938e7382017-06-26 20:48:39 -0400686 value = self.startup_edit.get()
687 self.add_changed_item('main', 'General', 'editor-on-startup', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000688
terryjreedy938e7382017-06-26 20:48:39 -0400689 def var_changed_autosave(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400690 "Store change to autosave."
terryjreedy938e7382017-06-26 20:48:39 -0400691 value = self.autosave.get()
692 self.add_changed_item('main', 'General', 'autosave', value)
Kurt B. Kaiser6c638b62003-05-26 06:23:10 +0000693
terryjreedy938e7382017-06-26 20:48:39 -0400694 def var_changed_encoding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400695 "Store change to encoding."
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400696 value = self.encoding.get()
terryjreedy938e7382017-06-26 20:48:39 -0400697 self.add_changed_item('main', 'EditorWindow', 'encoding', value)
Kurt B. Kaisera053f332003-05-10 00:49:56 +0000698
terryjreedy938e7382017-06-26 20:48:39 -0400699 def reset_changed_items(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400700 """Reset dictionary containing the items changed on each tab.
701
702 When any config item is changed in this dialog, an entry
703 should be made in the relevant section (config type) of this
704 dictionary. The key should be the config file section name and the
705 value a dictionary, whose key:value pairs are item=value pairs for
706 that config file section.
707 """
terryjreedy938e7382017-06-26 20:48:39 -0400708 self.changed_items = {'main':{}, 'highlight':{}, 'keys':{},
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400709 'extensions':{}}
Steven M. Gavaa498af22002-02-01 01:33:36 +0000710
terryjreedy938e7382017-06-26 20:48:39 -0400711 def add_changed_item(self, typ, section, item, value):
terryjreedye5bb1122017-07-05 00:54:55 -0400712 "Add item/value pair to changed items dictionary for typ and section."
713 value = str(value) # Make sure we use a string.
terryjreedy938e7382017-06-26 20:48:39 -0400714 if section not in self.changed_items[typ]:
715 self.changed_items[typ][section] = {}
716 self.changed_items[typ][section][item] = value
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000717
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000718 def GetDefaultItems(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400719 "Return dictionary of default configuration settings."
terryjreedy938e7382017-06-26 20:48:39 -0400720 d_items={'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}}
721 for config_type in d_items:
722 sections = idleConf.GetSectionList('default', config_type)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000723 for section in sections:
terryjreedy938e7382017-06-26 20:48:39 -0400724 d_items[config_type][section] = {}
725 options = idleConf.defaultCfg[config_type].GetOptionList(section)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000726 for option in options:
terryjreedy938e7382017-06-26 20:48:39 -0400727 d_items[config_type][section][option] = (
728 idleConf.defaultCfg[config_type].Get(section, option))
729 return d_items
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000730
terryjreedy938e7382017-06-26 20:48:39 -0400731 def set_theme_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400732 "Set available screen options based on builtin or custom theme."
terryjreedy938e7382017-06-26 20:48:39 -0400733 if self.is_builtin_theme.get():
734 self.opt_menu_theme_builtin.config(state=NORMAL)
735 self.opt_menu_theme_custom.config(state=DISABLED)
736 self.button_delete_custom_theme.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000737 else:
terryjreedy938e7382017-06-26 20:48:39 -0400738 self.opt_menu_theme_builtin.config(state=DISABLED)
739 self.radio_theme_custom.config(state=NORMAL)
740 self.opt_menu_theme_custom.config(state=NORMAL)
741 self.button_delete_custom_theme.config(state=NORMAL)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000742
terryjreedy938e7382017-06-26 20:48:39 -0400743 def set_keys_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400744 "Set available screen options based on builtin or custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400745 if self.are_keys_builtin.get():
746 self.opt_menu_keys_builtin.config(state=NORMAL)
747 self.opt_menu_keys_custom.config(state=DISABLED)
748 self.button_delete_custom_keys.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000749 else:
terryjreedy938e7382017-06-26 20:48:39 -0400750 self.opt_menu_keys_builtin.config(state=DISABLED)
751 self.radio_keys_custom.config(state=NORMAL)
752 self.opt_menu_keys_custom.config(state=NORMAL)
753 self.button_delete_custom_keys.config(state=NORMAL)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000754
terryjreedy938e7382017-06-26 20:48:39 -0400755 def get_new_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400756 """Handle event to change key binding for selected line.
757
758 A selection of a key/binding in the list of current
759 bindings pops up a dialog to enter a new binding. If
760 the current key set is builtin and a binding has
761 changed, then a name for a custom key set needs to be
762 entered for the change to be applied.
763 """
terryjreedy938e7382017-06-26 20:48:39 -0400764 list_index = self.list_bindings.index(ANCHOR)
765 binding = self.list_bindings.get(list_index)
terryjreedye5bb1122017-07-05 00:54:55 -0400766 bind_name = binding.split()[0]
terryjreedy938e7382017-06-26 20:48:39 -0400767 if self.are_keys_builtin.get():
768 current_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000769 else:
terryjreedy938e7382017-06-26 20:48:39 -0400770 current_key_set_name = self.custom_keys.get()
771 current_bindings = idleConf.GetCurrentKeySet()
terryjreedye5bb1122017-07-05 00:54:55 -0400772 if current_key_set_name in self.changed_items['keys']: # unsaved changes
terryjreedy938e7382017-06-26 20:48:39 -0400773 key_set_changes = self.changed_items['keys'][current_key_set_name]
774 for event in key_set_changes:
775 current_bindings[event] = key_set_changes[event].split()
776 current_key_sequences = list(current_bindings.values())
777 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
778 current_key_sequences).result
terryjreedye5bb1122017-07-05 00:54:55 -0400779 if new_keys:
780 if self.are_keys_builtin.get(): # Current key set is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400781 message = ('Your changes will be saved as a new Custom Key Set.'
782 ' Enter a name for your new Custom Key Set below.')
terryjreedy938e7382017-06-26 20:48:39 -0400783 new_keyset = self.get_new_keys_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400784 if not new_keyset: # User cancelled custom key set creation.
terryjreedy938e7382017-06-26 20:48:39 -0400785 self.list_bindings.select_set(list_index)
786 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000787 return
terryjreedye5bb1122017-07-05 00:54:55 -0400788 else: # Create new custom key set based on previously active key set.
terryjreedy938e7382017-06-26 20:48:39 -0400789 self.create_new_key_set(new_keyset)
790 self.list_bindings.delete(list_index)
791 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
792 self.list_bindings.select_set(list_index)
793 self.list_bindings.select_anchor(list_index)
794 self.keybinding.set(new_keys)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000795 else:
terryjreedy938e7382017-06-26 20:48:39 -0400796 self.list_bindings.select_set(list_index)
797 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000798
terryjreedy938e7382017-06-26 20:48:39 -0400799 def get_new_keys_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400800 "Return new key set name from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400801 used_names = (idleConf.GetSectionList('user', 'keys') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400802 idleConf.GetSectionList('default', 'keys'))
terryjreedy938e7382017-06-26 20:48:39 -0400803 new_keyset = SectionName(
804 self, 'New Custom Key Set', message, used_names).result
805 return new_keyset
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000806
terryjreedy938e7382017-06-26 20:48:39 -0400807 def save_as_new_key_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400808 "Prompt for name of new key set and save changes using that name."
terryjreedy938e7382017-06-26 20:48:39 -0400809 new_keys_name = self.get_new_keys_name('New Key Set Name:')
810 if new_keys_name:
811 self.create_new_key_set(new_keys_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000812
terryjreedy938e7382017-06-26 20:48:39 -0400813 def keybinding_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -0400814 "Activate button to assign new keys to selected action."
terryjreedy938e7382017-06-26 20:48:39 -0400815 self.button_new_keys.config(state=NORMAL)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000816
terryjreedy938e7382017-06-26 20:48:39 -0400817 def create_new_key_set(self, new_key_set_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400818 """Create a new custom key set with the given name.
819
820 Create the new key set based on the previously active set
821 with the current changes applied. Once it is saved, then
822 activate the new key set.
823 """
terryjreedy938e7382017-06-26 20:48:39 -0400824 if self.are_keys_builtin.get():
825 prev_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000826 else:
terryjreedy938e7382017-06-26 20:48:39 -0400827 prev_key_set_name = self.custom_keys.get()
828 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
829 new_keys = {}
terryjreedye5bb1122017-07-05 00:54:55 -0400830 for event in prev_keys: # Add key set to changed items.
831 event_name = event[2:-2] # Trim off the angle brackets.
terryjreedy938e7382017-06-26 20:48:39 -0400832 binding = ' '.join(prev_keys[event])
833 new_keys[event_name] = binding
terryjreedye5bb1122017-07-05 00:54:55 -0400834 # Handle any unsaved changes to prev key set.
terryjreedy938e7382017-06-26 20:48:39 -0400835 if prev_key_set_name in self.changed_items['keys']:
836 key_set_changes = self.changed_items['keys'][prev_key_set_name]
837 for event in key_set_changes:
838 new_keys[event] = key_set_changes[event]
terryjreedye5bb1122017-07-05 00:54:55 -0400839 # Save the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400840 self.save_new_key_set(new_key_set_name, new_keys)
terryjreedye5bb1122017-07-05 00:54:55 -0400841 # Change GUI over to the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400842 custom_key_list = idleConf.GetSectionList('user', 'keys')
843 custom_key_list.sort()
844 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
845 self.are_keys_builtin.set(0)
846 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000847
terryjreedy938e7382017-06-26 20:48:39 -0400848 def load_keys_list(self, keyset_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400849 """Reload the list of action/key binding pairs for the active key set.
850
851 An action/key binding can be selected to change the key binding.
852 """
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400853 reselect = 0
terryjreedye5bb1122017-07-05 00:54:55 -0400854 # XXX - new_keyset isn't used in this function.
terryjreedy938e7382017-06-26 20:48:39 -0400855 new_keyset = 0
856 if self.list_bindings.curselection():
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400857 reselect = 1
terryjreedy938e7382017-06-26 20:48:39 -0400858 list_index = self.list_bindings.index(ANCHOR)
859 keyset = idleConf.GetKeySet(keyset_name)
860 bind_names = list(keyset.keys())
861 bind_names.sort()
862 self.list_bindings.delete(0, END)
863 for bind_name in bind_names:
terryjreedye5bb1122017-07-05 00:54:55 -0400864 key = ' '.join(keyset[bind_name])
865 bind_name = bind_name[2:-2] # Trim off the angle brackets.
terryjreedy938e7382017-06-26 20:48:39 -0400866 if keyset_name in self.changed_items['keys']:
terryjreedye5bb1122017-07-05 00:54:55 -0400867 # Handle any unsaved changes to this key set.
terryjreedy938e7382017-06-26 20:48:39 -0400868 if bind_name in self.changed_items['keys'][keyset_name]:
869 key = self.changed_items['keys'][keyset_name][bind_name]
870 self.list_bindings.insert(END, bind_name+' - '+key)
Steven M. Gava052937f2002-02-11 02:20:53 +0000871 if reselect:
terryjreedy938e7382017-06-26 20:48:39 -0400872 self.list_bindings.see(list_index)
873 self.list_bindings.select_set(list_index)
874 self.list_bindings.select_anchor(list_index)
Steven M. Gava052937f2002-02-11 02:20:53 +0000875
terryjreedy938e7382017-06-26 20:48:39 -0400876 def delete_custom_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400877 """Handle event to delete a custom key set.
878
879 Applying the delete deactivates the current configuration and
880 reverts to the default. The custom key set is permanently
881 deleted from the config file.
882 """
terryjreedy938e7382017-06-26 20:48:39 -0400883 keyset_name=self.custom_keys.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400884 delmsg = 'Are you sure you wish to delete the key set %r ?'
885 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400886 'Delete Key Set', delmsg % keyset_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000887 return
terryjreedy938e7382017-06-26 20:48:39 -0400888 self.deactivate_current_config()
terryjreedye5bb1122017-07-05 00:54:55 -0400889 # Remove key set from config.
terryjreedy938e7382017-06-26 20:48:39 -0400890 idleConf.userCfg['keys'].remove_section(keyset_name)
891 if keyset_name in self.changed_items['keys']:
892 del(self.changed_items['keys'][keyset_name])
terryjreedye5bb1122017-07-05 00:54:55 -0400893 # Write changes.
Steven M. Gava49745752002-02-18 01:43:11 +0000894 idleConf.userCfg['keys'].Save()
terryjreedye5bb1122017-07-05 00:54:55 -0400895 # Reload user key set list.
terryjreedy938e7382017-06-26 20:48:39 -0400896 item_list = idleConf.GetSectionList('user', 'keys')
897 item_list.sort()
898 if not item_list:
899 self.radio_keys_custom.config(state=DISABLED)
900 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
Steven M. Gava49745752002-02-18 01:43:11 +0000901 else:
terryjreedy938e7382017-06-26 20:48:39 -0400902 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400903 # Revert to default key set.
terryjreedy938e7382017-06-26 20:48:39 -0400904 self.are_keys_builtin.set(idleConf.defaultCfg['main']
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400905 .Get('Keys', 'default'))
terryjreedy938e7382017-06-26 20:48:39 -0400906 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400907 or idleConf.default_keys())
terryjreedye5bb1122017-07-05 00:54:55 -0400908 # User can't back out of these changes, they must be applied now.
terryjreedy938e7382017-06-26 20:48:39 -0400909 self.save_all_changed_configs()
910 self.activate_config_changes()
911 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000912
terryjreedy938e7382017-06-26 20:48:39 -0400913 def delete_custom_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400914 """Handle event to delete custom theme.
915
916 The current theme is deactivated and the default theme is
917 activated. The custom theme is permanently removed from
918 the config file.
919 """
terryjreedy938e7382017-06-26 20:48:39 -0400920 theme_name = self.custom_theme.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400921 delmsg = 'Are you sure you wish to delete the theme %r ?'
922 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400923 'Delete Theme', delmsg % theme_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000924 return
terryjreedy938e7382017-06-26 20:48:39 -0400925 self.deactivate_current_config()
terryjreedye5bb1122017-07-05 00:54:55 -0400926 # Remove theme from config.
terryjreedy938e7382017-06-26 20:48:39 -0400927 idleConf.userCfg['highlight'].remove_section(theme_name)
928 if theme_name in self.changed_items['highlight']:
929 del(self.changed_items['highlight'][theme_name])
terryjreedye5bb1122017-07-05 00:54:55 -0400930 # Write changes.
Steven M. Gava49745752002-02-18 01:43:11 +0000931 idleConf.userCfg['highlight'].Save()
terryjreedye5bb1122017-07-05 00:54:55 -0400932 # Reload user theme list.
terryjreedy938e7382017-06-26 20:48:39 -0400933 item_list = idleConf.GetSectionList('user', 'highlight')
934 item_list.sort()
935 if not item_list:
936 self.radio_theme_custom.config(state=DISABLED)
937 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
Steven M. Gava49745752002-02-18 01:43:11 +0000938 else:
terryjreedy938e7382017-06-26 20:48:39 -0400939 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400940 # Revert to default theme.
terryjreedy938e7382017-06-26 20:48:39 -0400941 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
942 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
terryjreedye5bb1122017-07-05 00:54:55 -0400943 # User can't back out of these changes, they must be applied now.
terryjreedy938e7382017-06-26 20:48:39 -0400944 self.save_all_changed_configs()
945 self.activate_config_changes()
946 self.set_theme_type()
Steven M. Gava49745752002-02-18 01:43:11 +0000947
terryjreedy938e7382017-06-26 20:48:39 -0400948 def get_colour(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400949 """Handle button to select a new color for the target tag.
950
951 If a new color is selected while using a builtin theme, a
952 name must be supplied to create a custom theme.
953 """
terryjreedy938e7382017-06-26 20:48:39 -0400954 target = self.highlight_target.get()
955 prev_colour = self.frame_colour_set.cget('bg')
956 rgbTuplet, colour_string = tkColorChooser.askcolor(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400957 parent=self, title='Pick new colour for : '+target,
terryjreedy938e7382017-06-26 20:48:39 -0400958 initialcolor=prev_colour)
959 if colour_string and (colour_string != prev_colour):
terryjreedye5bb1122017-07-05 00:54:55 -0400960 # User didn't cancel and they chose a new colour.
961 if self.is_builtin_theme.get(): # Current theme is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400962 message = ('Your changes will be saved as a new Custom Theme. '
963 'Enter a name for your new Custom Theme below.')
terryjreedy938e7382017-06-26 20:48:39 -0400964 new_theme = self.get_new_theme_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400965 if not new_theme: # User cancelled custom theme creation.
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000966 return
terryjreedye5bb1122017-07-05 00:54:55 -0400967 else: # Create new custom theme based on previously active theme.
terryjreedy938e7382017-06-26 20:48:39 -0400968 self.create_new_theme(new_theme)
969 self.colour.set(colour_string)
terryjreedye5bb1122017-07-05 00:54:55 -0400970 else: # Current theme is user defined.
terryjreedy938e7382017-06-26 20:48:39 -0400971 self.colour.set(colour_string)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000972
terryjreedy938e7382017-06-26 20:48:39 -0400973 def on_new_colour_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400974 "Display sample of new color selection on the dialog."
terryjreedy938e7382017-06-26 20:48:39 -0400975 new_colour=self.colour.get()
terryjreedye5bb1122017-07-05 00:54:55 -0400976 self.frame_colour_set.config(bg=new_colour) # Set sample.
terryjreedy938e7382017-06-26 20:48:39 -0400977 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
978 sample_element = self.theme_elements[self.highlight_target.get()][0]
979 self.text_highlight_sample.tag_config(sample_element, **{plane:new_colour})
980 theme = self.custom_theme.get()
981 theme_element = sample_element + '-' + plane
982 self.add_changed_item('highlight', theme, theme_element, new_colour)
Steven M. Gava052937f2002-02-11 02:20:53 +0000983
terryjreedy938e7382017-06-26 20:48:39 -0400984 def get_new_theme_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400985 "Return name of new theme from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400986 used_names = (idleConf.GetSectionList('user', 'highlight') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400987 idleConf.GetSectionList('default', 'highlight'))
terryjreedy938e7382017-06-26 20:48:39 -0400988 new_theme = SectionName(
989 self, 'New Custom Theme', message, used_names).result
990 return new_theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000991
terryjreedy938e7382017-06-26 20:48:39 -0400992 def save_as_new_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400993 "Prompt for new theme name and create the theme."
terryjreedy938e7382017-06-26 20:48:39 -0400994 new_theme_name = self.get_new_theme_name('New Theme Name:')
995 if new_theme_name:
996 self.create_new_theme(new_theme_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000997
terryjreedy938e7382017-06-26 20:48:39 -0400998 def create_new_theme(self, new_theme_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400999 """Create a new custom theme with the given name.
1000
1001 Create the new theme based on the previously active theme
1002 with the current changes applied. Once it is saved, then
1003 activate the new theme.
1004 """
terryjreedy938e7382017-06-26 20:48:39 -04001005 if self.is_builtin_theme.get():
1006 theme_type = 'default'
1007 theme_name = self.builtin_theme.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001008 else:
terryjreedy938e7382017-06-26 20:48:39 -04001009 theme_type = 'user'
1010 theme_name = self.custom_theme.get()
1011 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
terryjreedye5bb1122017-07-05 00:54:55 -04001012 # Apply any of the old theme's unsaved changes to the new theme.
terryjreedy938e7382017-06-26 20:48:39 -04001013 if theme_name in self.changed_items['highlight']:
1014 theme_changes = self.changed_items['highlight'][theme_name]
1015 for element in theme_changes:
1016 new_theme[element] = theme_changes[element]
terryjreedye5bb1122017-07-05 00:54:55 -04001017 # Save the new theme.
terryjreedy938e7382017-06-26 20:48:39 -04001018 self.save_new_theme(new_theme_name, new_theme)
terryjreedye5bb1122017-07-05 00:54:55 -04001019 # Change GUI over to the new theme.
terryjreedy938e7382017-06-26 20:48:39 -04001020 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
1021 custom_theme_list.sort()
1022 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
1023 self.is_builtin_theme.set(0)
1024 self.set_theme_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001025
terryjreedy938e7382017-06-26 20:48:39 -04001026 def on_list_fonts_button_release(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001027 """Handle event of selecting a font from the list.
1028
1029 Change the font name to the font selected from the list
1030 and update sample text to show that font.
1031 """
terryjreedy938e7382017-06-26 20:48:39 -04001032 font = self.list_fonts.get(ANCHOR)
1033 self.font_name.set(font.lower())
1034 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001035
terryjreedy938e7382017-06-26 20:48:39 -04001036 def set_font_sample(self, event=None):
terryjreedye5bb1122017-07-05 00:54:55 -04001037 "Update the screen samples with the font settings from the dialog."
terryjreedy938e7382017-06-26 20:48:39 -04001038 font_name = self.font_name.get()
1039 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
1040 new_font = (font_name, self.font_size.get(), font_weight)
1041 self.font_sample.config(font=new_font)
1042 self.text_highlight_sample.configure(font=new_font)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001043
terryjreedy938e7382017-06-26 20:48:39 -04001044 def set_highlight_target(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001045 "Set fg/bg toggle and color based on highlight tag target."
1046 if self.highlight_target.get() == 'Cursor': # bg not possible
terryjreedy938e7382017-06-26 20:48:39 -04001047 self.radio_fg.config(state=DISABLED)
1048 self.radio_bg.config(state=DISABLED)
1049 self.fg_bg_toggle.set(1)
terryjreedye5bb1122017-07-05 00:54:55 -04001050 else: # Both fg and bg can be set.
terryjreedy938e7382017-06-26 20:48:39 -04001051 self.radio_fg.config(state=NORMAL)
1052 self.radio_bg.config(state=NORMAL)
1053 self.fg_bg_toggle.set(1)
1054 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001055
terryjreedy938e7382017-06-26 20:48:39 -04001056 def set_colour_sample_binding(self, *args):
terryjreedye5bb1122017-07-05 00:54:55 -04001057 "Change color sample based on foreground/background toggle."
terryjreedy938e7382017-06-26 20:48:39 -04001058 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001059
terryjreedy938e7382017-06-26 20:48:39 -04001060 def set_colour_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001061 "Set the color of the frame background to reflect the selected target."
1062 # Set the colour sample area.
terryjreedy938e7382017-06-26 20:48:39 -04001063 tag = self.theme_elements[self.highlight_target.get()][0]
1064 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1065 colour = self.text_highlight_sample.tag_cget(tag, plane)
1066 self.frame_colour_set.config(bg=colour)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001067
terryjreedy938e7382017-06-26 20:48:39 -04001068 def paint_theme_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001069 "Apply the theme colors to each element tag in the sample text."
1070 if self.is_builtin_theme.get(): # Default theme
terryjreedy938e7382017-06-26 20:48:39 -04001071 theme = self.builtin_theme.get()
terryjreedye5bb1122017-07-05 00:54:55 -04001072 else: # User theme
terryjreedy938e7382017-06-26 20:48:39 -04001073 theme = self.custom_theme.get()
1074 for element_title in self.theme_elements:
1075 element = self.theme_elements[element_title][0]
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001076 colours = idleConf.GetHighlight(theme, element)
terryjreedye5bb1122017-07-05 00:54:55 -04001077 if element == 'cursor': # Cursor sample needs special painting.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001078 colours['background'] = idleConf.GetHighlight(
1079 theme, 'normal', fgBg='bg')
terryjreedye5bb1122017-07-05 00:54:55 -04001080 # Handle any unsaved changes to this theme.
terryjreedy938e7382017-06-26 20:48:39 -04001081 if theme in self.changed_items['highlight']:
1082 theme_dict = self.changed_items['highlight'][theme]
1083 if element + '-foreground' in theme_dict:
1084 colours['foreground'] = theme_dict[element + '-foreground']
1085 if element + '-background' in theme_dict:
1086 colours['background'] = theme_dict[element + '-background']
1087 self.text_highlight_sample.tag_config(element, **colours)
1088 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001089
terryjreedy938e7382017-06-26 20:48:39 -04001090 def help_source_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001091 "Handle event for selecting additional help."
terryjreedy938e7382017-06-26 20:48:39 -04001092 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001093
terryjreedy938e7382017-06-26 20:48:39 -04001094 def set_helplist_button_states(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001095 "Toggle the state for the help list buttons based on list entries."
1096 if self.list_help.size() < 1: # No entries in list.
terryjreedy938e7382017-06-26 20:48:39 -04001097 self.button_helplist_edit.config(state=DISABLED)
1098 self.button_helplist_remove.config(state=DISABLED)
terryjreedye5bb1122017-07-05 00:54:55 -04001099 else: # Some entries.
1100 if self.list_help.curselection(): # There currently is a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001101 self.button_helplist_edit.config(state=NORMAL)
1102 self.button_helplist_remove.config(state=NORMAL)
terryjreedye5bb1122017-07-05 00:54:55 -04001103 else: # There currently is not a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001104 self.button_helplist_edit.config(state=DISABLED)
1105 self.button_helplist_remove.config(state=DISABLED)
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001106
terryjreedy938e7382017-06-26 20:48:39 -04001107 def helplist_item_add(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001108 """Handle add button for the help list.
1109
1110 Query for name and location of new help sources and add
1111 them to the list.
1112 """
terryjreedy938e7382017-06-26 20:48:39 -04001113 help_source = HelpSource(self, 'New Help Source',
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001114 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001115 if help_source:
1116 self.user_helplist.append((help_source[0], help_source[1]))
1117 self.list_help.insert(END, help_source[0])
1118 self.update_user_help_changed_items()
1119 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001120
terryjreedy938e7382017-06-26 20:48:39 -04001121 def helplist_item_edit(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001122 """Handle edit button for the help list.
1123
1124 Query with existing help source information and update
1125 config if the values are changed.
1126 """
terryjreedy938e7382017-06-26 20:48:39 -04001127 item_index = self.list_help.index(ANCHOR)
1128 help_source = self.user_helplist[item_index]
1129 new_help_source = HelpSource(
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001130 self, 'Edit Help Source',
terryjreedy938e7382017-06-26 20:48:39 -04001131 menuitem=help_source[0],
1132 filepath=help_source[1],
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001133 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001134 if new_help_source and new_help_source != help_source:
1135 self.user_helplist[item_index] = new_help_source
1136 self.list_help.delete(item_index)
1137 self.list_help.insert(item_index, new_help_source[0])
1138 self.update_user_help_changed_items()
1139 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001140
terryjreedy938e7382017-06-26 20:48:39 -04001141 def helplist_item_remove(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001142 """Handle remove button for the help list.
1143
1144 Delete the help list item from config.
1145 """
terryjreedy938e7382017-06-26 20:48:39 -04001146 item_index = self.list_help.index(ANCHOR)
1147 del(self.user_helplist[item_index])
1148 self.list_help.delete(item_index)
1149 self.update_user_help_changed_items()
1150 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001151
terryjreedy938e7382017-06-26 20:48:39 -04001152 def update_user_help_changed_items(self):
1153 "Clear and rebuild the HelpFiles section in self.changed_items"
1154 self.changed_items['main']['HelpFiles'] = {}
1155 for num in range(1, len(self.user_helplist) + 1):
1156 self.add_changed_item(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001157 'main', 'HelpFiles', str(num),
terryjreedy938e7382017-06-26 20:48:39 -04001158 ';'.join(self.user_helplist[num-1][:2]))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001159
terryjreedy938e7382017-06-26 20:48:39 -04001160 def load_font_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001161 "Load current configuration settings for the font options."
1162 # Set base editor font selection list.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001163 fonts = list(tkFont.families(self))
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001164 fonts.sort()
1165 for font in fonts:
terryjreedy938e7382017-06-26 20:48:39 -04001166 self.list_fonts.insert(END, font)
1167 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
1168 font_name = configured_font[0].lower()
1169 font_size = configured_font[1]
1170 font_bold = configured_font[2]=='bold'
1171 self.font_name.set(font_name)
Kurt B. Kaiser05391692003-05-26 20:35:53 +00001172 lc_fonts = [s.lower() for s in fonts]
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001173 try:
terryjreedy938e7382017-06-26 20:48:39 -04001174 current_font_index = lc_fonts.index(font_name)
1175 self.list_fonts.see(current_font_index)
1176 self.list_fonts.select_set(current_font_index)
1177 self.list_fonts.select_anchor(current_font_index)
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001178 except ValueError:
1179 pass
terryjreedye5bb1122017-07-05 00:54:55 -04001180 # Set font size dropdown.
terryjreedy938e7382017-06-26 20:48:39 -04001181 self.opt_menu_font_size.SetMenu(('7', '8', '9', '10', '11', '12', '13',
Terry Jan Reedyda028872016-08-30 20:19:13 -04001182 '14', '16', '18', '20', '22',
terryjreedy938e7382017-06-26 20:48:39 -04001183 '25', '29', '34', '40'), font_size )
terryjreedye5bb1122017-07-05 00:54:55 -04001184 # Set font weight.
terryjreedy938e7382017-06-26 20:48:39 -04001185 self.font_bold.set(font_bold)
terryjreedye5bb1122017-07-05 00:54:55 -04001186 # Set font sample.
terryjreedy938e7382017-06-26 20:48:39 -04001187 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001188
terryjreedy938e7382017-06-26 20:48:39 -04001189 def load_tab_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001190 "Load current configuration settings for the tab options."
1191 # Set indent sizes.
terryjreedy938e7382017-06-26 20:48:39 -04001192 space_num = idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001193 'main', 'Indent', 'num-spaces', default=4, type='int')
terryjreedy938e7382017-06-26 20:48:39 -04001194 self.space_num.set(space_num)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001195
terryjreedy938e7382017-06-26 20:48:39 -04001196 def load_theme_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001197 "Load current configuration settings for the theme options."
1198 # Set current theme type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001199 self.is_builtin_theme.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001200 'main', 'Theme', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001201 # Set current theme.
terryjreedy938e7382017-06-26 20:48:39 -04001202 current_option = idleConf.CurrentTheme()
terryjreedye5bb1122017-07-05 00:54:55 -04001203 # Load available theme option menus.
1204 if self.is_builtin_theme.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001205 item_list = idleConf.GetSectionList('default', 'highlight')
1206 item_list.sort()
1207 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
1208 item_list = idleConf.GetSectionList('user', 'highlight')
1209 item_list.sort()
1210 if not item_list:
1211 self.radio_theme_custom.config(state=DISABLED)
1212 self.custom_theme.set('- no custom themes -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001213 else:
terryjreedy938e7382017-06-26 20:48:39 -04001214 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001215 else: # User theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001216 item_list = idleConf.GetSectionList('user', 'highlight')
1217 item_list.sort()
1218 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
1219 item_list = idleConf.GetSectionList('default', 'highlight')
1220 item_list.sort()
1221 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
1222 self.set_theme_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001223 # Load theme element option menu.
terryjreedy938e7382017-06-26 20:48:39 -04001224 theme_names = list(self.theme_elements.keys())
1225 theme_names.sort(key=lambda x: self.theme_elements[x][1])
1226 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
1227 self.paint_theme_sample()
1228 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001229
terryjreedy938e7382017-06-26 20:48:39 -04001230 def load_key_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001231 "Load current configuration settings for the keybinding options."
1232 # Set current keys type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001233 self.are_keys_builtin.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001234 'main', 'Keys', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001235 # Set current keys.
terryjreedy938e7382017-06-26 20:48:39 -04001236 current_option = idleConf.CurrentKeys()
terryjreedye5bb1122017-07-05 00:54:55 -04001237 # Load available keyset option menus.
1238 if self.are_keys_builtin.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001239 item_list = idleConf.GetSectionList('default', 'keys')
1240 item_list.sort()
1241 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1242 item_list = idleConf.GetSectionList('user', 'keys')
1243 item_list.sort()
1244 if not item_list:
1245 self.radio_keys_custom.config(state=DISABLED)
1246 self.custom_keys.set('- no custom keys -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001247 else:
terryjreedy938e7382017-06-26 20:48:39 -04001248 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001249 else: # User key set selected.
terryjreedy938e7382017-06-26 20:48:39 -04001250 item_list = idleConf.GetSectionList('user', 'keys')
1251 item_list.sort()
1252 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1253 item_list = idleConf.GetSectionList('default', 'keys')
1254 item_list.sort()
1255 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1256 self.set_keys_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001257 # Load keyset element list.
terryjreedy938e7382017-06-26 20:48:39 -04001258 keyset_name = idleConf.CurrentKeys()
1259 self.load_keys_list(keyset_name)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001260
terryjreedy938e7382017-06-26 20:48:39 -04001261 def load_general_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001262 "Load current configuration settings for the general options."
1263 # Set startup state.
terryjreedy938e7382017-06-26 20:48:39 -04001264 self.startup_edit.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001265 'main', 'General', 'editor-on-startup', default=1, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001266 # Set autosave state.
terryjreedy938e7382017-06-26 20:48:39 -04001267 self.autosave.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001268 'main', 'General', 'autosave', default=0, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001269 # Set initial window size.
terryjreedy938e7382017-06-26 20:48:39 -04001270 self.win_width.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001271 'main', 'EditorWindow', 'width', type='int'))
terryjreedy938e7382017-06-26 20:48:39 -04001272 self.win_height.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001273 'main', 'EditorWindow', 'height', type='int'))
terryjreedye5bb1122017-07-05 00:54:55 -04001274 # Set default source encoding.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001275 self.encoding.set(idleConf.GetOption(
1276 'main', 'EditorWindow', 'encoding', default='none'))
terryjreedye5bb1122017-07-05 00:54:55 -04001277 # Set additional help sources.
terryjreedy938e7382017-06-26 20:48:39 -04001278 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1279 for help_item in self.user_helplist:
1280 self.list_help.insert(END, help_item[0])
1281 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001282
terryjreedy938e7382017-06-26 20:48:39 -04001283 def load_configs(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001284 """Load configuration for each page.
1285
1286 Load configuration from default and user config files and populate
Steven M. Gava429a86af2001-10-23 10:42:12 +00001287 the widgets on the config dialog pages.
1288 """
terryjreedy938e7382017-06-26 20:48:39 -04001289 self.load_font_cfg()
1290 self.load_tab_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001291 self.load_theme_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001292 self.load_key_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001293 self.load_general_cfg()
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001294 # note: extension page handled separately
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001295
terryjreedy938e7382017-06-26 20:48:39 -04001296 def save_new_key_set(self, keyset_name, keyset):
terryjreedye5bb1122017-07-05 00:54:55 -04001297 """Save a newly created core key set.
1298
terryjreedy938e7382017-06-26 20:48:39 -04001299 keyset_name - string, the name of the new key set
1300 keyset - dictionary containing the new key set
Steven M. Gava052937f2002-02-11 02:20:53 +00001301 """
terryjreedy938e7382017-06-26 20:48:39 -04001302 if not idleConf.userCfg['keys'].has_section(keyset_name):
1303 idleConf.userCfg['keys'].add_section(keyset_name)
1304 for event in keyset:
1305 value = keyset[event]
1306 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001307
terryjreedy938e7382017-06-26 20:48:39 -04001308 def save_new_theme(self, theme_name, theme):
terryjreedye5bb1122017-07-05 00:54:55 -04001309 """Save a newly created theme.
1310
terryjreedy938e7382017-06-26 20:48:39 -04001311 theme_name - string, the name of the new theme
Steven M. Gava052937f2002-02-11 02:20:53 +00001312 theme - dictionary containing the new theme
1313 """
terryjreedy938e7382017-06-26 20:48:39 -04001314 if not idleConf.userCfg['highlight'].has_section(theme_name):
1315 idleConf.userCfg['highlight'].add_section(theme_name)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001316 for element in theme:
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001317 value = theme[element]
terryjreedy938e7382017-06-26 20:48:39 -04001318 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001319
terryjreedy938e7382017-06-26 20:48:39 -04001320 def set_user_value(self, config_type, section, item, value):
terryjreedye5bb1122017-07-05 00:54:55 -04001321 "Return True if the configuration value was added or changed."
terryjreedy938e7382017-06-26 20:48:39 -04001322 if idleConf.defaultCfg[config_type].has_option(section, item):
1323 if idleConf.defaultCfg[config_type].Get(section, item) == value:
terryjreedye5bb1122017-07-05 00:54:55 -04001324 # The setting equals a default setting, remove it from user cfg.
terryjreedy938e7382017-06-26 20:48:39 -04001325 return idleConf.userCfg[config_type].RemoveOption(section, item)
terryjreedye5bb1122017-07-05 00:54:55 -04001326 # If we got here, set the option.
terryjreedy938e7382017-06-26 20:48:39 -04001327 return idleConf.userCfg[config_type].SetOption(section, item, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001328
terryjreedy938e7382017-06-26 20:48:39 -04001329 def save_all_changed_configs(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001330 "Save all configuration changes to the user config file."
Steven M. Gava0c5bc8c2002-03-27 02:25:44 +00001331 idleConf.userCfg['main'].Save()
terryjreedy938e7382017-06-26 20:48:39 -04001332 for config_type in self.changed_items:
1333 cfg_type_changed = False
1334 for section in self.changed_items[config_type]:
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +00001335 if section == 'HelpFiles':
terryjreedye5bb1122017-07-05 00:54:55 -04001336 # This section gets completely replaced.
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +00001337 idleConf.userCfg['main'].remove_section('HelpFiles')
terryjreedy938e7382017-06-26 20:48:39 -04001338 cfg_type_changed = True
1339 for item in self.changed_items[config_type][section]:
1340 value = self.changed_items[config_type][section][item]
1341 if self.set_user_value(config_type, section, item, value):
1342 cfg_type_changed = True
1343 if cfg_type_changed:
1344 idleConf.userCfg[config_type].Save()
1345 for config_type in ['keys', 'highlight']:
terryjreedye5bb1122017-07-05 00:54:55 -04001346 # Save these even if unchanged!
terryjreedy938e7382017-06-26 20:48:39 -04001347 idleConf.userCfg[config_type].Save()
terryjreedye5bb1122017-07-05 00:54:55 -04001348 self.reset_changed_items() # Clear the changed items dict.
1349 self.save_all_changed_extensions() # Uses a different mechanism.
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001350
terryjreedy938e7382017-06-26 20:48:39 -04001351 def deactivate_current_config(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001352 "Remove current key bindings."
1353 # Before a config is saved, some cleanup of current
1354 # config must be done - remove the previous keybindings.
terryjreedy938e7382017-06-26 20:48:39 -04001355 win_instances = self.parent.instance_dict.keys()
1356 for instance in win_instances:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001357 instance.RemoveKeybindings()
1358
terryjreedy938e7382017-06-26 20:48:39 -04001359 def activate_config_changes(self):
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001360 "Dynamically apply configuration changes"
terryjreedy938e7382017-06-26 20:48:39 -04001361 win_instances = self.parent.instance_dict.keys()
1362 for instance in win_instances:
Steven M. Gavab77d3432002-03-02 07:16:21 +00001363 instance.ResetColorizer()
Steven M. Gavab1585412002-03-12 00:21:56 +00001364 instance.ResetFont()
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001365 instance.set_notabs_indentwidth()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001366 instance.ApplyKeybindings()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +00001367 instance.reset_help_menu_entries()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001368
terryjreedy938e7382017-06-26 20:48:39 -04001369 def cancel(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001370 "Dismiss config dialog."
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001371 self.destroy()
1372
terryjreedy938e7382017-06-26 20:48:39 -04001373 def ok(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001374 "Apply config changes, then dismiss dialog."
terryjreedy938e7382017-06-26 20:48:39 -04001375 self.apply()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001376 self.destroy()
1377
terryjreedy938e7382017-06-26 20:48:39 -04001378 def apply(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001379 "Apply config changes and leave dialog open."
terryjreedy938e7382017-06-26 20:48:39 -04001380 self.deactivate_current_config()
1381 self.save_all_changed_configs()
1382 self.activate_config_changes()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001383
terryjreedy938e7382017-06-26 20:48:39 -04001384 def help(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001385 "Create textview for config dialog help."
terryjreedy938e7382017-06-26 20:48:39 -04001386 page = self.tab_pages._current_page
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001387 view_text(self, title='Help for IDLE preferences',
1388 text=help_common+help_pages.get(page, ''))
1389
terryjreedy938e7382017-06-26 20:48:39 -04001390 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001391 """Part of the config dialog used for configuring IDLE extensions.
1392
1393 This code is generic - it works for any and all IDLE extensions.
1394
1395 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001396 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001397 GUI interface to change the configuration values, and saves the
1398 changes using idleConf.
1399
1400 Not all changes take effect immediately - some may require restarting IDLE.
1401 This depends on each extension's implementation.
1402
1403 All values are treated as text, and it is up to the user to supply
1404 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001405 which are boolean, and can be toggled with a True/False button.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001406 """
1407 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -04001408 frame = self.tab_pages.pages['Extensions'].frame
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001409 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1410 self.ext_userCfg = idleConf.userCfg['extensions']
1411 self.is_int = self.register(is_int)
1412 self.load_extensions()
terryjreedye5bb1122017-07-05 00:54:55 -04001413 # Create widgets - a listbox shows all available extensions, with the
1414 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001415 self.extension_names = StringVar(self)
1416 frame.rowconfigure(0, weight=1)
1417 frame.columnconfigure(2, weight=1)
1418 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1419 selectmode='browse')
1420 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1421 scroll = Scrollbar(frame, command=self.extension_list.yview)
1422 self.extension_list.yscrollcommand=scroll.set
1423 self.details_frame = LabelFrame(frame, width=250, height=250)
1424 self.extension_list.grid(column=0, row=0, sticky='nws')
1425 scroll.grid(column=1, row=0, sticky='ns')
1426 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1427 frame.configure(padx=10, pady=10)
1428 self.config_frame = {}
1429 self.current_extension = None
1430
1431 self.outerframe = self # TEMPORARY
1432 self.tabbed_page_set = self.extension_list # TEMPORARY
1433
terryjreedye5bb1122017-07-05 00:54:55 -04001434 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001435 ext_names = ''
1436 for ext_name in sorted(self.extensions):
1437 self.create_extension_frame(ext_name)
1438 ext_names = ext_names + '{' + ext_name + '} '
1439 self.extension_names.set(ext_names)
1440 self.extension_list.selection_set(0)
1441 self.extension_selected(None)
1442
1443 def load_extensions(self):
1444 "Fill self.extensions with data from the default and user configs."
1445 self.extensions = {}
1446 for ext_name in idleConf.GetExtensions(active_only=False):
1447 self.extensions[ext_name] = []
1448
1449 for ext_name in self.extensions:
1450 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1451
terryjreedye5bb1122017-07-05 00:54:55 -04001452 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001453 enables = [opt_name for opt_name in opt_list
1454 if opt_name.startswith('enable')]
1455 for opt_name in enables:
1456 opt_list.remove(opt_name)
1457 opt_list = enables + opt_list
1458
1459 for opt_name in opt_list:
1460 def_str = self.ext_defaultCfg.Get(
1461 ext_name, opt_name, raw=True)
1462 try:
1463 def_obj = {'True':True, 'False':False}[def_str]
1464 opt_type = 'bool'
1465 except KeyError:
1466 try:
1467 def_obj = int(def_str)
1468 opt_type = 'int'
1469 except ValueError:
1470 def_obj = def_str
1471 opt_type = None
1472 try:
1473 value = self.ext_userCfg.Get(
1474 ext_name, opt_name, type=opt_type, raw=True,
1475 default=def_obj)
terryjreedye5bb1122017-07-05 00:54:55 -04001476 except ValueError: # Need this until .Get fixed.
1477 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001478 var = StringVar(self)
1479 var.set(str(value))
1480
1481 self.extensions[ext_name].append({'name': opt_name,
1482 'type': opt_type,
1483 'default': def_str,
1484 'value': value,
1485 'var': var,
1486 })
1487
1488 def extension_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001489 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001490 newsel = self.extension_list.curselection()
1491 if newsel:
1492 newsel = self.extension_list.get(newsel)
1493 if newsel is None or newsel != self.current_extension:
1494 if self.current_extension:
1495 self.details_frame.config(text='')
1496 self.config_frame[self.current_extension].grid_forget()
1497 self.current_extension = None
1498 if newsel:
1499 self.details_frame.config(text=newsel)
1500 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1501 self.current_extension = newsel
1502
1503 def create_extension_frame(self, ext_name):
1504 """Create a frame holding the widgets to configure one extension"""
1505 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1506 self.config_frame[ext_name] = f
1507 entry_area = f.interior
terryjreedye5bb1122017-07-05 00:54:55 -04001508 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001509 for row, opt in enumerate(self.extensions[ext_name]):
terryjreedye5bb1122017-07-05 00:54:55 -04001510 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001511 label = Label(entry_area, text=opt['name'])
1512 label.grid(row=row, column=0, sticky=NW)
1513 var = opt['var']
1514 if opt['type'] == 'bool':
1515 Checkbutton(entry_area, textvariable=var, variable=var,
1516 onvalue='True', offvalue='False',
1517 indicatoron=FALSE, selectcolor='', width=8
1518 ).grid(row=row, column=1, sticky=W, padx=7)
1519 elif opt['type'] == 'int':
1520 Entry(entry_area, textvariable=var, validate='key',
1521 validatecommand=(self.is_int, '%P')
1522 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1523
1524 else:
1525 Entry(entry_area, textvariable=var
1526 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1527 return
1528
1529 def set_extension_value(self, section, opt):
terryjreedye5bb1122017-07-05 00:54:55 -04001530 """Return True if the configuration was added or changed.
1531
1532 If the value is the same as the default, then remove it
1533 from user config file.
1534 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001535 name = opt['name']
1536 default = opt['default']
1537 value = opt['var'].get().strip() or default
1538 opt['var'].set(value)
1539 # if self.defaultCfg.has_section(section):
terryjreedye5bb1122017-07-05 00:54:55 -04001540 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001541 if (value == default):
1542 return self.ext_userCfg.RemoveOption(section, name)
terryjreedye5bb1122017-07-05 00:54:55 -04001543 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001544 return self.ext_userCfg.SetOption(section, name, value)
1545
1546 def save_all_changed_extensions(self):
1547 """Save configuration changes to the user config file."""
1548 has_changes = False
1549 for ext_name in self.extensions:
1550 options = self.extensions[ext_name]
1551 for opt in options:
1552 if self.set_extension_value(ext_name, opt):
1553 has_changes = True
1554 if has_changes:
1555 self.ext_userCfg.Save()
1556
1557
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001558help_common = '''\
1559When you click either the Apply or Ok buttons, settings in this
1560dialog that are different from IDLE's default are saved in
1561a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001562these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001563machine. Some do not take affect until IDLE is restarted.
1564[Cancel] only cancels changes made since the last save.
1565'''
1566help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001567 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001568Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001569The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001570be used with older IDLE releases if it is saved as a custom
1571theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001572''',
1573 'Keys': '''
1574Keys:
1575The IDLE Modern Unix key set is new in June 2016. It can only
1576be used with older IDLE releases if it is saved as a custom
1577key set, with a different name.
1578''',
terryjreedyaf683822017-06-27 23:02:19 -04001579 'Extensions': '''
1580Extensions:
1581
1582Autocomplete: Popupwait is milleseconds to wait after key char, without
1583cursor movement, before popping up completion box. Key char is '.' after
1584identifier or a '/' (or '\\' on Windows) within a string.
1585
1586FormatParagraph: Max-width is max chars in lines after re-formatting.
1587Use with paragraphs in both strings and comment blocks.
1588
1589ParenMatch: Style indicates what is highlighted when closer is entered:
1590'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1591'expression' (default) - also everything in between. Flash-delay is how
1592long to highlight if cursor is not moved (0 means forever).
1593'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001594}
1595
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001596
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001597def is_int(s):
1598 "Return 's is blank or represents an int'"
1599 if not s:
1600 return True
1601 try:
1602 int(s)
1603 return True
1604 except ValueError:
1605 return False
1606
1607
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001608class VerticalScrolledFrame(Frame):
1609 """A pure Tkinter vertically scrollable frame.
1610
1611 * Use the 'interior' attribute to place widgets inside the scrollable frame
1612 * Construct and pack/place/grid normally
1613 * This frame only allows vertical scrolling
1614 """
1615 def __init__(self, parent, *args, **kw):
1616 Frame.__init__(self, parent, *args, **kw)
1617
terryjreedye5bb1122017-07-05 00:54:55 -04001618 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001619 vscrollbar = Scrollbar(self, orient=VERTICAL)
1620 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1621 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001622 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001623 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1624 vscrollbar.config(command=canvas.yview)
1625
terryjreedye5bb1122017-07-05 00:54:55 -04001626 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001627 canvas.xview_moveto(0)
1628 canvas.yview_moveto(0)
1629
terryjreedye5bb1122017-07-05 00:54:55 -04001630 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001631 self.interior = interior = Frame(canvas)
1632 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1633
terryjreedye5bb1122017-07-05 00:54:55 -04001634 # Track changes to the canvas and frame width and sync them,
1635 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001636 def _configure_interior(event):
terryjreedye5bb1122017-07-05 00:54:55 -04001637 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001638 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1639 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001640 interior.bind('<Configure>', _configure_interior)
1641
1642 def _configure_canvas(event):
1643 if interior.winfo_reqwidth() != canvas.winfo_width():
terryjreedye5bb1122017-07-05 00:54:55 -04001644 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001645 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1646 canvas.bind('<Configure>', _configure_canvas)
1647
1648 return
1649
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001650
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00001651if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04001652 import unittest
1653 unittest.main('idlelib.idle_test.test_configdialog',
1654 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04001655 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04001656 run(ConfigDialog)