blob: ade6710001d69ac8d1dca1f984725e21951f6753 [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
terryjreedyedc03422017-07-07 16:37:39 -040023from idlelib.config import idleConf, ConfigChanges
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040024from idlelib.config_key import GetKeysDialog
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040025from idlelib.dynoption import DynOptionMenu
26from idlelib import macosx
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -040027from idlelib.query import SectionName, HelpSource
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040028from idlelib.tabbedpages import TabbedPageSet
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -040029from idlelib.textview import view_text
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -040030
terryjreedyedc03422017-07-07 16:37:39 -040031changes = ConfigChanges()
32
terryjreedye5bb1122017-07-05 00:54:55 -040033
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000034class ConfigDialog(Toplevel):
terryjreedye5bb1122017-07-05 00:54:55 -040035 """Config dialog for IDLE.
36 """
Kurt B. Kaiseracdef852005-01-31 03:34:26 +000037
Terry Jan Reedycd567362014-10-17 01:31:35 -040038 def __init__(self, parent, title='', _htest=False, _utest=False):
terryjreedye5bb1122017-07-05 00:54:55 -040039 """Show the tabbed dialog for user configuration.
40
41 parent - parent of this dialog
42 title - string which is the title of this popup dialog
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040043 _htest - bool, change box location when running htest
Terry Jan Reedycfa89502014-07-14 23:07:32 -040044 _utest - bool, don't wait_window when running unittest
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -040045 """
Steven M. Gavad721c482001-07-31 10:46:53 +000046 Toplevel.__init__(self, parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -040047 self.parent = parent
Terry Jan Reedy4036d872014-08-03 23:02:58 -040048 if _htest:
49 parent.instance_dict = {}
terryjreedy938e7382017-06-26 20:48:39 -040050 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000051
Steven M. Gavad721c482001-07-31 10:46:53 +000052 self.configure(borderwidth=5)
Terry Jan Reedycd567362014-10-17 01:31:35 -040053 self.title(title or 'IDLE Preferences')
terryjreedy938e7382017-06-26 20:48:39 -040054 x = parent.winfo_rootx() + 20
55 y = parent.winfo_rooty() + (30 if not _htest else 150)
56 self.geometry(f'+{x}+{y}')
terryjreedye5bb1122017-07-05 00:54:55 -040057 # Each theme element key is its display name.
58 # The first value of the tuple is the sample area tag name.
59 # The second value is the display name list sort index.
terryjreedy938e7382017-06-26 20:48:39 -040060 self.theme_elements={
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -040061 'Normal Text': ('normal', '00'),
62 'Python Keywords': ('keyword', '01'),
63 'Python Definitions': ('definition', '02'),
64 'Python Builtins': ('builtin', '03'),
65 'Python Comments': ('comment', '04'),
66 'Python Strings': ('string', '05'),
67 'Selected Text': ('hilite', '06'),
68 'Found Text': ('hit', '07'),
69 'Cursor': ('cursor', '08'),
70 'Editor Breakpoint': ('break', '09'),
71 'Shell Normal Text': ('console', '10'),
72 'Shell Error Text': ('error', '11'),
73 'Shell Stdout Text': ('stdout', '12'),
74 'Shell Stderr Text': ('stderr', '13'),
Kurt B. Kaiser73360a32004-03-08 18:15:31 +000075 }
terryjreedy938e7382017-06-26 20:48:39 -040076 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040077 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000078 self.transient(parent)
79 self.grab_set()
terryjreedy938e7382017-06-26 20:48:39 -040080 self.protocol("WM_DELETE_WINDOW", self.cancel)
81 self.tab_pages.focus_set()
terryjreedye5bb1122017-07-05 00:54:55 -040082 # XXX Decide whether to keep or delete these key bindings.
83 # Key bindings for this dialog.
84 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
85 # self.bind('<Alt-a>', self.Apply) #apply changes, save
86 # self.bind('<F1>', self.Help) #context help
terryjreedy938e7382017-06-26 20:48:39 -040087 self.load_configs()
terryjreedye5bb1122017-07-05 00:54:55 -040088 self.attach_var_callbacks() # Avoid callbacks during load_configs.
Guido van Rossum8ce8a782007-11-01 19:42:39 +000089
Terry Jan Reedycfa89502014-07-14 23:07:32 -040090 if not _utest:
91 self.wm_deiconify()
92 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000093
terryjreedy938e7382017-06-26 20:48:39 -040094 def create_widgets(self):
terryjreedye5bb1122017-07-05 00:54:55 -040095 "Create and place widgets for tabbed dialog."
terryjreedy938e7382017-06-26 20:48:39 -040096 self.tab_pages = TabbedPageSet(self,
Terry Jan Reedy93f35422015-10-13 22:03:51 -040097 page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
98 'Extensions'])
terryjreedy938e7382017-06-26 20:48:39 -040099 self.tab_pages.pack(side=TOP, expand=TRUE, fill=BOTH)
100 self.create_page_font_tab()
101 self.create_page_highlight()
102 self.create_page_keys()
103 self.create_page_general()
104 self.create_page_extensions()
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400105 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400106
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400107 def create_action_buttons(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400108 "Return frame of action buttons for dialog."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400109 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400110 # Changing the default padding on OSX results in unreadable
terryjreedye5bb1122017-07-05 00:54:55 -0400111 # text in the buttons.
terryjreedy938e7382017-06-26 20:48:39 -0400112 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000113 else:
terryjreedy938e7382017-06-26 20:48:39 -0400114 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400115 outer = Frame(self, pady=2)
116 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400117 for txt, cmd in (
terryjreedy938e7382017-06-26 20:48:39 -0400118 ('Ok', self.ok),
119 ('Apply', self.apply),
120 ('Cancel', self.cancel),
121 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400122 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
terryjreedy938e7382017-06-26 20:48:39 -0400123 **padding_args).pack(side=LEFT, padx=5)
terryjreedye5bb1122017-07-05 00:54:55 -0400124 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400125 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
126 buttons.pack(side=BOTTOM)
127 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400128
terryjreedy938e7382017-06-26 20:48:39 -0400129 def create_page_font_tab(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400130 """Return frame of widgets for Font/Tabs tab.
131
132 Configuration attributes:
133 font_size: Font size.
134 font_bold: Select font bold or not.
135 font_name: Font face.
136 space_num: Indentation width.
137 edit_font: Font widget with default font name, size, and weight.
138 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400139 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400140 self.font_size = StringVar(parent)
141 self.font_bold = BooleanVar(parent)
142 self.font_name = StringVar(parent)
143 self.space_num = IntVar(parent)
144 self.edit_font = tkFont.Font(parent, ('courier', 10, 'normal'))
Terry Jan Reedy22405332014-07-30 19:24:32 -0400145
Steven M. Gavaf213ccb2001-08-05 08:00:28 +0000146 ##widget creation
147 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400148 frame = self.tab_pages.pages['Fonts/Tabs'].frame
Steven M. Gavaf213ccb2001-08-05 08:00:28 +0000149 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400150 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400151 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
terryjreedy938e7382017-06-26 20:48:39 -0400152 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400153 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
terryjreedy938e7382017-06-26 20:48:39 -0400154 #frame_font
155 frame_font_name = Frame(frame_font)
156 frame_font_param = Frame(frame_font)
157 font_name_title = Label(
158 frame_font_name, justify=LEFT, text='Font Face :')
159 self.list_fonts = Listbox(
160 frame_font_name, height=5, takefocus=FALSE, exportselection=FALSE)
161 self.list_fonts.bind(
162 '<ButtonRelease-1>', self.on_list_fonts_button_release)
163 scroll_font = Scrollbar(frame_font_name)
164 scroll_font.config(command=self.list_fonts.yview)
165 self.list_fonts.config(yscrollcommand=scroll_font.set)
166 font_size_title = Label(frame_font_param, text='Size :')
167 self.opt_menu_font_size = DynOptionMenu(
168 frame_font_param, self.font_size, None, command=self.set_font_sample)
169 check_font_bold = Checkbutton(
170 frame_font_param, variable=self.font_bold, onvalue=1,
171 offvalue=0, text='Bold', command=self.set_font_sample)
172 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
173 self.font_sample = Label(
174 frame_font_sample, justify=LEFT, font=self.edit_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400175 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
terryjreedy938e7382017-06-26 20:48:39 -0400176 #frame_indent
177 frame_indent_size = Frame(frame_indent)
178 indent_size_title = Label(
179 frame_indent_size, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400180 text='Python Standard: 4 Spaces!')
terryjreedy938e7382017-06-26 20:48:39 -0400181 self.scale_indent_size = Scale(
182 frame_indent_size, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400183 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400184
Steven M. Gavaf213ccb2001-08-05 08:00:28 +0000185 #widget packing
186 #body
terryjreedy938e7382017-06-26 20:48:39 -0400187 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
188 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
189 #frame_font
190 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
191 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
192 font_name_title.pack(side=TOP, anchor=W)
193 self.list_fonts.pack(side=LEFT, expand=TRUE, fill=X)
194 scroll_font.pack(side=LEFT, fill=Y)
195 font_size_title.pack(side=LEFT, anchor=W)
196 self.opt_menu_font_size.pack(side=LEFT, anchor=W)
197 check_font_bold.pack(side=LEFT, anchor=W, padx=20)
198 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
199 self.font_sample.pack(expand=TRUE, fill=BOTH)
200 #frame_indent
201 frame_indent_size.pack(side=TOP, fill=X)
202 indent_size_title.pack(side=TOP, anchor=W, padx=5)
203 self.scale_indent_size.pack(side=TOP, padx=5, fill=X)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000204 return frame
205
terryjreedy938e7382017-06-26 20:48:39 -0400206 def create_page_highlight(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400207 """Return frame of widgets for Highlighting tab.
208
209 Configuration attributes:
210 builtin_theme: Menu variable for built-in theme.
211 custom_theme: Menu variable for custom theme.
212 fg_bg_toggle: Toggle for foreground/background color.
213 colour: Color of selected target.
214 is_builtin_theme: Selector for built-in or custom theme.
215 highlight_target: Menu variable for the highlight tag target.
216 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400217 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400218 self.builtin_theme = StringVar(parent)
219 self.custom_theme = StringVar(parent)
220 self.fg_bg_toggle = BooleanVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400221 self.colour = StringVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400222 # XXX - font_name is defined in create_page_font_tab. Needed here too?
terryjreedy938e7382017-06-26 20:48:39 -0400223 self.font_name = StringVar(parent)
224 self.is_builtin_theme = BooleanVar(parent)
225 self.highlight_target = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400226
Steven M. Gava952d0a52001-08-03 04:43:44 +0000227 ##widget creation
228 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400229 frame = self.tab_pages.pages['Highlighting'].frame
Steven M. Gava952d0a52001-08-03 04:43:44 +0000230 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400231 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400232 text=' Custom Highlighting ')
terryjreedy938e7382017-06-26 20:48:39 -0400233 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400234 text=' Highlighting Theme ')
terryjreedy938e7382017-06-26 20:48:39 -0400235 #frame_custom
236 self.text_highlight_sample=Text(
237 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400238 font=('courier', 12, ''), cursor='hand2', width=21, height=11,
239 takefocus=FALSE, highlightthickness=0, wrap=NONE)
terryjreedy938e7382017-06-26 20:48:39 -0400240 text=self.text_highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400241 text.bind('<Double-Button-1>', lambda e: 'break')
242 text.bind('<B1-Motion>', lambda e: 'break')
terryjreedy938e7382017-06-26 20:48:39 -0400243 text_and_tags=(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400244 ('#you can click here', 'comment'), ('\n', 'normal'),
245 ('#to choose items', 'comment'), ('\n', 'normal'),
246 ('def', 'keyword'), (' ', 'normal'),
247 ('func', 'definition'), ('(param):\n ', 'normal'),
248 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
249 ("'string'", 'string'), ('\n var1 = ', 'normal'),
250 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
251 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
252 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400253 ('None', 'keyword'), (')\n', 'normal'),
254 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400255 (' error ', 'error'), (' ', 'normal'),
256 ('cursor |', 'cursor'), ('\n ', 'normal'),
257 ('shell', 'console'), (' ', 'normal'),
258 ('stdout', 'stdout'), (' ', 'normal'),
259 ('stderr', 'stderr'), ('\n', 'normal'))
terryjreedy938e7382017-06-26 20:48:39 -0400260 for texttag in text_and_tags:
261 text.insert(END, texttag[0], texttag[1])
262 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400263 def tem(event, elem=element):
terryjreedy938e7382017-06-26 20:48:39 -0400264 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400265 text.tag_bind(
terryjreedy938e7382017-06-26 20:48:39 -0400266 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Steven M. Gavae16d94b2001-11-03 05:07:28 +0000267 text.config(state=DISABLED)
terryjreedy938e7382017-06-26 20:48:39 -0400268 self.frame_colour_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
269 frame_fg_bg_toggle = Frame(frame_custom)
270 button_set_colour = Button(
271 self.frame_colour_set, text='Choose Colour for :',
272 command=self.get_colour, highlightthickness=0)
273 self.opt_menu_highlight_target = DynOptionMenu(
274 self.frame_colour_set, self.highlight_target, None,
275 highlightthickness=0) #, command=self.set_highlight_targetBinding
276 self.radio_fg = Radiobutton(
277 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
278 text='Foreground', command=self.set_colour_sample_binding)
279 self.radio_bg=Radiobutton(
280 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
281 text='Background', command=self.set_colour_sample_binding)
282 self.fg_bg_toggle.set(1)
283 button_save_custom_theme = Button(
284 frame_custom, text='Save as New Custom Theme',
285 command=self.save_as_new_theme)
286 #frame_theme
287 theme_type_title = Label(frame_theme, text='Select : ')
288 self.radio_theme_builtin = Radiobutton(
289 frame_theme, variable=self.is_builtin_theme, value=1,
290 command=self.set_theme_type, text='a Built-in Theme')
291 self.radio_theme_custom = Radiobutton(
292 frame_theme, variable=self.is_builtin_theme, value=0,
293 command=self.set_theme_type, text='a Custom Theme')
294 self.opt_menu_theme_builtin = DynOptionMenu(
295 frame_theme, self.builtin_theme, None, command=None)
296 self.opt_menu_theme_custom=DynOptionMenu(
297 frame_theme, self.custom_theme, None, command=None)
298 self.button_delete_custom_theme=Button(
299 frame_theme, text='Delete Custom Theme',
300 command=self.delete_custom_theme)
301 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400302
Steven M. Gava952d0a52001-08-03 04:43:44 +0000303 ##widget packing
304 #body
terryjreedy938e7382017-06-26 20:48:39 -0400305 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
306 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
307 #frame_custom
308 self.frame_colour_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
309 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
310 self.text_highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400311 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
terryjreedy938e7382017-06-26 20:48:39 -0400312 button_set_colour.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
313 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400314 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
terryjreedy938e7382017-06-26 20:48:39 -0400315 self.radio_fg.pack(side=LEFT, anchor=E)
316 self.radio_bg.pack(side=RIGHT, anchor=W)
317 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
318 #frame_theme
319 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
320 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
321 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
322 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
323 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
324 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500325 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000326 return frame
327
terryjreedy938e7382017-06-26 20:48:39 -0400328 def create_page_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400329 """Return frame of widgets for Keys tab.
330
331 Configuration attributes:
332 builtin_keys: Menu variable for built-in keybindings.
333 custom_keys: Menu variable for custom keybindings.
334 are_keys_builtin: Selector for built-in or custom keybindings.
335 keybinding: Action/key bindings.
336 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400337 parent = self.parent
terryjreedye5bb1122017-07-05 00:54:55 -0400338 # XXX - binding_target isn't used.
terryjreedy938e7382017-06-26 20:48:39 -0400339 self.binding_target = StringVar(parent)
340 self.builtin_keys = StringVar(parent)
341 self.custom_keys = StringVar(parent)
342 self.are_keys_builtin = BooleanVar(parent)
343 self.keybinding = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400344
Steven M. Gava60fc7072001-08-04 13:58:22 +0000345 ##widget creation
346 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400347 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +0000348 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400349 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400350 frame, borderwidth=2, relief=GROOVE,
351 text=' Custom Key Bindings ')
terryjreedy938e7382017-06-26 20:48:39 -0400352 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400353 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
terryjreedy938e7382017-06-26 20:48:39 -0400354 #frame_custom
355 frame_target = Frame(frame_custom)
356 target_title = Label(frame_target, text='Action - Key(s)')
357 scroll_target_y = Scrollbar(frame_target)
358 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
359 self.list_bindings = Listbox(
360 frame_target, takefocus=FALSE, exportselection=FALSE)
361 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
362 scroll_target_y.config(command=self.list_bindings.yview)
363 scroll_target_x.config(command=self.list_bindings.xview)
364 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
365 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
366 self.button_new_keys = Button(
367 frame_custom, text='Get New Keys for Selection',
368 command=self.get_new_keys, state=DISABLED)
369 #frame_key_sets
370 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +0000371 for i in range(2)]
terryjreedy938e7382017-06-26 20:48:39 -0400372 self.radio_keys_builtin = Radiobutton(
373 frames[0], variable=self.are_keys_builtin, value=1,
374 command=self.set_keys_type, text='Use a Built-in Key Set')
375 self.radio_keys_custom = Radiobutton(
376 frames[0], variable=self.are_keys_builtin, value=0,
377 command=self.set_keys_type, text='Use a Custom Key Set')
378 self.opt_menu_keys_builtin = DynOptionMenu(
379 frames[0], self.builtin_keys, None, command=None)
380 self.opt_menu_keys_custom = DynOptionMenu(
381 frames[0], self.custom_keys, None, command=None)
382 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400383 frames[1], text='Delete Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400384 command=self.delete_custom_keys)
385 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400386 frames[1], text='Save as New Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400387 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400388 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400389
Steven M. Gava60fc7072001-08-04 13:58:22 +0000390 ##widget packing
391 #body
terryjreedy938e7382017-06-26 20:48:39 -0400392 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
393 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
394 #frame_custom
395 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
396 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +0000397 #frame target
terryjreedy938e7382017-06-26 20:48:39 -0400398 frame_target.columnconfigure(0, weight=1)
399 frame_target.rowconfigure(1, weight=1)
400 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
401 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
402 scroll_target_y.grid(row=1, column=1, sticky=NS)
403 scroll_target_x.grid(row=2, column=0, sticky=EW)
404 #frame_key_sets
405 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
406 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
407 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
408 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400409 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
terryjreedy938e7382017-06-26 20:48:39 -0400410 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
411 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +0000412 frames[0].pack(side=TOP, fill=BOTH, expand=True)
413 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000414 return frame
415
terryjreedy938e7382017-06-26 20:48:39 -0400416 def create_page_general(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400417 """Return frame of widgets for General tab.
418
419 Configuration attributes:
420 win_width: Initial window width in characters.
421 win_height: Initial window height in characters.
422 startup_edit: Selector for opening in editor or shell mode.
423 autosave: Selector for save prompt popup when using Run.
424 encoding: ?
425 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400426 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400427 self.win_width = StringVar(parent)
428 self.win_height = StringVar(parent)
429 self.startup_edit = IntVar(parent)
430 self.autosave = IntVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400431 # XXX - encoding isn't on the screen to be set, but is saved to config.
Terry Jan Reedy22405332014-07-30 19:24:32 -0400432 self.encoding = StringVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400433 # XXX - user_help_browser and help_browser aren't used.
terryjreedy938e7382017-06-26 20:48:39 -0400434 self.user_help_browser = BooleanVar(parent)
435 self.help_browser = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400436
Steven M. Gava230e5782001-08-07 03:28:25 +0000437 #widget creation
438 #body
terryjreedy938e7382017-06-26 20:48:39 -0400439 frame = self.tab_pages.pages['General'].frame
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000440 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400441 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400442 text=' Startup Preferences ')
terryjreedy938e7382017-06-26 20:48:39 -0400443 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
444 text=' autosave Preferences ')
445 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
446 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400447 text=' Additional Help Sources ')
terryjreedy938e7382017-06-26 20:48:39 -0400448 #frame_run
449 startup_title = Label(frame_run, text='At Startup')
450 self.radio_startup_edit = Radiobutton(
451 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500452 text="Open Edit Window")
terryjreedy938e7382017-06-26 20:48:39 -0400453 self.radio_startup_shell = Radiobutton(
454 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500455 text='Open Shell Window')
terryjreedy938e7382017-06-26 20:48:39 -0400456 #frame_save
457 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
458 self.radio_save_ask = Radiobutton(
459 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500460 text="Prompt to Save")
terryjreedy938e7382017-06-26 20:48:39 -0400461 self.radio_save_auto = Radiobutton(
462 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500463 text='No Prompt')
terryjreedy938e7382017-06-26 20:48:39 -0400464 #frame_win_size
465 win_size_title = Label(
466 frame_win_size, text='Initial Window Size (in characters)')
467 win_width_title = Label(frame_win_size, text='Width')
468 self.entry_win_width = Entry(
469 frame_win_size, textvariable=self.win_width, width=3)
470 win_height_title = Label(frame_win_size, text='Height')
471 self.entry_win_height = Entry(
472 frame_win_size, textvariable=self.win_height, width=3)
473 #frame_help
474 frame_helplist = Frame(frame_help)
475 frame_helplist_buttons = Frame(frame_helplist)
476 scroll_helplist = Scrollbar(frame_helplist)
477 self.list_help = Listbox(
478 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000479 exportselection=FALSE)
terryjreedy938e7382017-06-26 20:48:39 -0400480 scroll_helplist.config(command=self.list_help.yview)
481 self.list_help.config(yscrollcommand=scroll_helplist.set)
482 self.list_help.bind('<ButtonRelease-1>', self.help_source_selected)
483 self.button_helplist_edit = Button(
484 frame_helplist_buttons, text='Edit', state=DISABLED,
485 width=8, command=self.helplist_item_edit)
486 self.button_helplist_add = Button(
487 frame_helplist_buttons, text='Add',
488 width=8, command=self.helplist_item_add)
489 self.button_helplist_remove = Button(
490 frame_helplist_buttons, text='Remove', state=DISABLED,
491 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400492
Steven M. Gava230e5782001-08-07 03:28:25 +0000493 #widget packing
494 #body
terryjreedy938e7382017-06-26 20:48:39 -0400495 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
496 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
497 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
498 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
499 #frame_run
500 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
501 self.radio_startup_shell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
502 self.radio_startup_edit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
503 #frame_save
504 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
505 self.radio_save_auto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
506 self.radio_save_ask.pack(side=RIGHT, anchor=W, padx=5, pady=5)
507 #frame_win_size
508 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
509 self.entry_win_height.pack(side=RIGHT, anchor=E, padx=10, pady=5)
510 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
511 self.entry_win_width.pack(side=RIGHT, anchor=E, padx=10, pady=5)
512 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
513 #frame_help
514 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
515 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
516 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
517 self.list_help.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
518 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
519 self.button_helplist_add.pack(side=TOP, anchor=W)
520 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000521 return frame
522
terryjreedy938e7382017-06-26 20:48:39 -0400523 def attach_var_callbacks(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400524 "Attach callbacks to variables that can be changed."
terryjreedy938e7382017-06-26 20:48:39 -0400525 self.font_size.trace_add('write', self.var_changed_font)
526 self.font_name.trace_add('write', self.var_changed_font)
527 self.font_bold.trace_add('write', self.var_changed_font)
528 self.space_num.trace_add('write', self.var_changed_space_num)
529 self.colour.trace_add('write', self.var_changed_colour)
530 self.builtin_theme.trace_add('write', self.var_changed_builtin_theme)
531 self.custom_theme.trace_add('write', self.var_changed_custom_theme)
532 self.is_builtin_theme.trace_add('write', self.var_changed_is_builtin_theme)
533 self.highlight_target.trace_add('write', self.var_changed_highlight_target)
534 self.keybinding.trace_add('write', self.var_changed_keybinding)
535 self.builtin_keys.trace_add('write', self.var_changed_builtin_keys)
536 self.custom_keys.trace_add('write', self.var_changed_custom_keys)
537 self.are_keys_builtin.trace_add('write', self.var_changed_are_keys_builtin)
538 self.win_width.trace_add('write', self.var_changed_win_width)
539 self.win_height.trace_add('write', self.var_changed_win_height)
540 self.startup_edit.trace_add('write', self.var_changed_startup_edit)
541 self.autosave.trace_add('write', self.var_changed_autosave)
542 self.encoding.trace_add('write', self.var_changed_encoding)
Steven M. Gava052937f2002-02-11 02:20:53 +0000543
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400544 def remove_var_callbacks(self):
545 "Remove callbacks to prevent memory leaks."
546 for var in (
terryjreedy938e7382017-06-26 20:48:39 -0400547 self.font_size, self.font_name, self.font_bold,
548 self.space_num, self.colour, self.builtin_theme,
549 self.custom_theme, self.is_builtin_theme, self.highlight_target,
550 self.keybinding, self.builtin_keys, self.custom_keys,
551 self.are_keys_builtin, self.win_width, self.win_height,
552 self.startup_edit, self.autosave, self.encoding,):
Serhiy Storchaka81221742016-06-26 09:46:57 +0300553 var.trace_remove('write', var.trace_info()[0][1])
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400554
terryjreedy938e7382017-06-26 20:48:39 -0400555 def var_changed_font(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400556 """Store changes to font attributes.
557
558 When one font attribute changes, save them all, as they are
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400559 not independent from each other. In particular, when we are
560 overriding the default font, we need to write out everything.
terryjreedye5bb1122017-07-05 00:54:55 -0400561 """
terryjreedy938e7382017-06-26 20:48:39 -0400562 value = self.font_name.get()
terryjreedyedc03422017-07-07 16:37:39 -0400563 changes.add_option('main', 'EditorWindow', 'font', value)
terryjreedy938e7382017-06-26 20:48:39 -0400564 value = self.font_size.get()
terryjreedyedc03422017-07-07 16:37:39 -0400565 changes.add_option('main', 'EditorWindow', 'font-size', value)
terryjreedy938e7382017-06-26 20:48:39 -0400566 value = self.font_bold.get()
terryjreedyedc03422017-07-07 16:37:39 -0400567 changes.add_option('main', 'EditorWindow', 'font-bold', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000568
terryjreedy938e7382017-06-26 20:48:39 -0400569 def var_changed_space_num(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400570 "Store change to indentation size."
terryjreedy938e7382017-06-26 20:48:39 -0400571 value = self.space_num.get()
terryjreedyedc03422017-07-07 16:37:39 -0400572 changes.add_option('main', 'Indent', 'num-spaces', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000573
terryjreedy938e7382017-06-26 20:48:39 -0400574 def var_changed_colour(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400575 "Process change to color choice."
terryjreedy938e7382017-06-26 20:48:39 -0400576 self.on_new_colour_set()
Steven M. Gava052937f2002-02-11 02:20:53 +0000577
terryjreedy938e7382017-06-26 20:48:39 -0400578 def var_changed_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400579 """Process new builtin theme selection.
580
581 Add the changed theme's name to the changed_items and recreate
582 the sample with the values from the selected theme.
583 """
terryjreedy938e7382017-06-26 20:48:39 -0400584 old_themes = ('IDLE Classic', 'IDLE New')
585 value = self.builtin_theme.get()
586 if value not in old_themes:
587 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
terryjreedyedc03422017-07-07 16:37:39 -0400588 changes.add_option('main', 'Theme', 'name', old_themes[0])
589 changes.add_option('main', 'Theme', 'name2', value)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500590 self.new_custom_theme.config(text='New theme, see Help',
591 fg='#500000')
592 else:
terryjreedyedc03422017-07-07 16:37:39 -0400593 changes.add_option('main', 'Theme', 'name', value)
594 changes.add_option('main', 'Theme', 'name2', '')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500595 self.new_custom_theme.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400596 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000597
terryjreedy938e7382017-06-26 20:48:39 -0400598 def var_changed_custom_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400599 """Process new custom theme selection.
600
601 If a new custom theme is selected, add the name to the
602 changed_items and apply the theme to the sample.
603 """
terryjreedy938e7382017-06-26 20:48:39 -0400604 value = self.custom_theme.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000605 if value != '- no custom themes -':
terryjreedyedc03422017-07-07 16:37:39 -0400606 changes.add_option('main', 'Theme', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400607 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000608
terryjreedy938e7382017-06-26 20:48:39 -0400609 def var_changed_is_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400610 """Process toggle between builtin and custom theme.
611
612 Update the default toggle value and apply the newly
613 selected theme type.
614 """
terryjreedy938e7382017-06-26 20:48:39 -0400615 value = self.is_builtin_theme.get()
terryjreedyedc03422017-07-07 16:37:39 -0400616 changes.add_option('main', 'Theme', 'default', value)
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000617 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400618 self.var_changed_builtin_theme()
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000619 else:
terryjreedy938e7382017-06-26 20:48:39 -0400620 self.var_changed_custom_theme()
Steven M. Gava052937f2002-02-11 02:20:53 +0000621
terryjreedy938e7382017-06-26 20:48:39 -0400622 def var_changed_highlight_target(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400623 "Process selection of new target tag for highlighting."
terryjreedy938e7382017-06-26 20:48:39 -0400624 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000625
terryjreedy938e7382017-06-26 20:48:39 -0400626 def var_changed_keybinding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400627 "Store change to a keybinding."
terryjreedy938e7382017-06-26 20:48:39 -0400628 value = self.keybinding.get()
629 key_set = self.custom_keys.get()
630 event = self.list_bindings.get(ANCHOR).split()[0]
Steven M. Gavaa498af22002-02-01 01:33:36 +0000631 if idleConf.IsCoreBinding(event):
terryjreedyedc03422017-07-07 16:37:39 -0400632 changes.add_option('keys', key_set, event, value)
terryjreedye5bb1122017-07-05 00:54:55 -0400633 else: # Event is an extension binding.
terryjreedy938e7382017-06-26 20:48:39 -0400634 ext_name = idleConf.GetExtnNameForEvent(event)
635 ext_keybind_section = ext_name + '_cfgBindings'
terryjreedyedc03422017-07-07 16:37:39 -0400636 changes.add_option('extensions', ext_keybind_section, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000637
terryjreedy938e7382017-06-26 20:48:39 -0400638 def var_changed_builtin_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400639 "Process selection of builtin key set."
terryjreedy938e7382017-06-26 20:48:39 -0400640 old_keys = (
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400641 'IDLE Classic Windows',
642 'IDLE Classic Unix',
643 'IDLE Classic Mac',
644 'IDLE Classic OSX',
645 )
terryjreedy938e7382017-06-26 20:48:39 -0400646 value = self.builtin_keys.get()
647 if value not in old_keys:
648 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
terryjreedyedc03422017-07-07 16:37:39 -0400649 changes.add_option('main', 'Keys', 'name', old_keys[0])
650 changes.add_option('main', 'Keys', 'name2', value)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400651 self.new_custom_keys.config(text='New key set, see Help',
652 fg='#500000')
653 else:
terryjreedyedc03422017-07-07 16:37:39 -0400654 changes.add_option('main', 'Keys', 'name', value)
655 changes.add_option('main', 'Keys', 'name2', '')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400656 self.new_custom_keys.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400657 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000658
terryjreedy938e7382017-06-26 20:48:39 -0400659 def var_changed_custom_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400660 "Process selection of custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400661 value = self.custom_keys.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000662 if value != '- no custom keys -':
terryjreedyedc03422017-07-07 16:37:39 -0400663 changes.add_option('main', 'Keys', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400664 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000665
terryjreedy938e7382017-06-26 20:48:39 -0400666 def var_changed_are_keys_builtin(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400667 "Process toggle between builtin key set and custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400668 value = self.are_keys_builtin.get()
terryjreedyedc03422017-07-07 16:37:39 -0400669 changes.add_option('main', 'Keys', 'default', value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000670 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400671 self.var_changed_builtin_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000672 else:
terryjreedy938e7382017-06-26 20:48:39 -0400673 self.var_changed_custom_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000674
terryjreedy938e7382017-06-26 20:48:39 -0400675 def var_changed_win_width(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400676 "Store change to window width."
terryjreedy938e7382017-06-26 20:48:39 -0400677 value = self.win_width.get()
terryjreedyedc03422017-07-07 16:37:39 -0400678 changes.add_option('main', 'EditorWindow', 'width', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000679
terryjreedy938e7382017-06-26 20:48:39 -0400680 def var_changed_win_height(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400681 "Store change to window height."
terryjreedy938e7382017-06-26 20:48:39 -0400682 value = self.win_height.get()
terryjreedyedc03422017-07-07 16:37:39 -0400683 changes.add_option('main', 'EditorWindow', 'height', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000684
terryjreedy938e7382017-06-26 20:48:39 -0400685 def var_changed_startup_edit(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400686 "Store change to toggle for starting IDLE in the editor or shell."
terryjreedy938e7382017-06-26 20:48:39 -0400687 value = self.startup_edit.get()
terryjreedyedc03422017-07-07 16:37:39 -0400688 changes.add_option('main', 'General', 'editor-on-startup', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000689
terryjreedy938e7382017-06-26 20:48:39 -0400690 def var_changed_autosave(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400691 "Store change to autosave."
terryjreedy938e7382017-06-26 20:48:39 -0400692 value = self.autosave.get()
terryjreedyedc03422017-07-07 16:37:39 -0400693 changes.add_option('main', 'General', 'autosave', value)
Kurt B. Kaiser6c638b62003-05-26 06:23:10 +0000694
terryjreedy938e7382017-06-26 20:48:39 -0400695 def var_changed_encoding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400696 "Store change to encoding."
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400697 value = self.encoding.get()
terryjreedyedc03422017-07-07 16:37:39 -0400698 changes.add_option('main', 'EditorWindow', 'encoding', value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000699
terryjreedy938e7382017-06-26 20:48:39 -0400700 def set_theme_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400701 "Set available screen options based on builtin or custom theme."
terryjreedy938e7382017-06-26 20:48:39 -0400702 if self.is_builtin_theme.get():
703 self.opt_menu_theme_builtin.config(state=NORMAL)
704 self.opt_menu_theme_custom.config(state=DISABLED)
705 self.button_delete_custom_theme.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000706 else:
terryjreedy938e7382017-06-26 20:48:39 -0400707 self.opt_menu_theme_builtin.config(state=DISABLED)
708 self.radio_theme_custom.config(state=NORMAL)
709 self.opt_menu_theme_custom.config(state=NORMAL)
710 self.button_delete_custom_theme.config(state=NORMAL)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000711
terryjreedy938e7382017-06-26 20:48:39 -0400712 def set_keys_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400713 "Set available screen options based on builtin or custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400714 if self.are_keys_builtin.get():
715 self.opt_menu_keys_builtin.config(state=NORMAL)
716 self.opt_menu_keys_custom.config(state=DISABLED)
717 self.button_delete_custom_keys.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000718 else:
terryjreedy938e7382017-06-26 20:48:39 -0400719 self.opt_menu_keys_builtin.config(state=DISABLED)
720 self.radio_keys_custom.config(state=NORMAL)
721 self.opt_menu_keys_custom.config(state=NORMAL)
722 self.button_delete_custom_keys.config(state=NORMAL)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000723
terryjreedy938e7382017-06-26 20:48:39 -0400724 def get_new_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400725 """Handle event to change key binding for selected line.
726
727 A selection of a key/binding in the list of current
728 bindings pops up a dialog to enter a new binding. If
729 the current key set is builtin and a binding has
730 changed, then a name for a custom key set needs to be
731 entered for the change to be applied.
732 """
terryjreedy938e7382017-06-26 20:48:39 -0400733 list_index = self.list_bindings.index(ANCHOR)
734 binding = self.list_bindings.get(list_index)
terryjreedye5bb1122017-07-05 00:54:55 -0400735 bind_name = binding.split()[0]
terryjreedy938e7382017-06-26 20:48:39 -0400736 if self.are_keys_builtin.get():
737 current_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000738 else:
terryjreedy938e7382017-06-26 20:48:39 -0400739 current_key_set_name = self.custom_keys.get()
740 current_bindings = idleConf.GetCurrentKeySet()
terryjreedyedc03422017-07-07 16:37:39 -0400741 if current_key_set_name in changes['keys']: # unsaved changes
742 key_set_changes = changes['keys'][current_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400743 for event in key_set_changes:
744 current_bindings[event] = key_set_changes[event].split()
745 current_key_sequences = list(current_bindings.values())
746 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
747 current_key_sequences).result
terryjreedye5bb1122017-07-05 00:54:55 -0400748 if new_keys:
749 if self.are_keys_builtin.get(): # Current key set is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400750 message = ('Your changes will be saved as a new Custom Key Set.'
751 ' Enter a name for your new Custom Key Set below.')
terryjreedy938e7382017-06-26 20:48:39 -0400752 new_keyset = self.get_new_keys_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400753 if not new_keyset: # User cancelled custom key set creation.
terryjreedy938e7382017-06-26 20:48:39 -0400754 self.list_bindings.select_set(list_index)
755 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000756 return
terryjreedye5bb1122017-07-05 00:54:55 -0400757 else: # Create new custom key set based on previously active key set.
terryjreedy938e7382017-06-26 20:48:39 -0400758 self.create_new_key_set(new_keyset)
759 self.list_bindings.delete(list_index)
760 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
761 self.list_bindings.select_set(list_index)
762 self.list_bindings.select_anchor(list_index)
763 self.keybinding.set(new_keys)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000764 else:
terryjreedy938e7382017-06-26 20:48:39 -0400765 self.list_bindings.select_set(list_index)
766 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000767
terryjreedy938e7382017-06-26 20:48:39 -0400768 def get_new_keys_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400769 "Return new key set name from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400770 used_names = (idleConf.GetSectionList('user', 'keys') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400771 idleConf.GetSectionList('default', 'keys'))
terryjreedy938e7382017-06-26 20:48:39 -0400772 new_keyset = SectionName(
773 self, 'New Custom Key Set', message, used_names).result
774 return new_keyset
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000775
terryjreedy938e7382017-06-26 20:48:39 -0400776 def save_as_new_key_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400777 "Prompt for name of new key set and save changes using that name."
terryjreedy938e7382017-06-26 20:48:39 -0400778 new_keys_name = self.get_new_keys_name('New Key Set Name:')
779 if new_keys_name:
780 self.create_new_key_set(new_keys_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000781
terryjreedy938e7382017-06-26 20:48:39 -0400782 def keybinding_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -0400783 "Activate button to assign new keys to selected action."
terryjreedy938e7382017-06-26 20:48:39 -0400784 self.button_new_keys.config(state=NORMAL)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000785
terryjreedy938e7382017-06-26 20:48:39 -0400786 def create_new_key_set(self, new_key_set_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400787 """Create a new custom key set with the given name.
788
789 Create the new key set based on the previously active set
790 with the current changes applied. Once it is saved, then
791 activate the new key set.
792 """
terryjreedy938e7382017-06-26 20:48:39 -0400793 if self.are_keys_builtin.get():
794 prev_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000795 else:
terryjreedy938e7382017-06-26 20:48:39 -0400796 prev_key_set_name = self.custom_keys.get()
797 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
798 new_keys = {}
terryjreedye5bb1122017-07-05 00:54:55 -0400799 for event in prev_keys: # Add key set to changed items.
800 event_name = event[2:-2] # Trim off the angle brackets.
terryjreedy938e7382017-06-26 20:48:39 -0400801 binding = ' '.join(prev_keys[event])
802 new_keys[event_name] = binding
terryjreedye5bb1122017-07-05 00:54:55 -0400803 # Handle any unsaved changes to prev key set.
terryjreedyedc03422017-07-07 16:37:39 -0400804 if prev_key_set_name in changes['keys']:
805 key_set_changes = changes['keys'][prev_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400806 for event in key_set_changes:
807 new_keys[event] = key_set_changes[event]
terryjreedye5bb1122017-07-05 00:54:55 -0400808 # Save the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400809 self.save_new_key_set(new_key_set_name, new_keys)
terryjreedye5bb1122017-07-05 00:54:55 -0400810 # Change GUI over to the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400811 custom_key_list = idleConf.GetSectionList('user', 'keys')
812 custom_key_list.sort()
813 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
814 self.are_keys_builtin.set(0)
815 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000816
terryjreedy938e7382017-06-26 20:48:39 -0400817 def load_keys_list(self, keyset_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400818 """Reload the list of action/key binding pairs for the active key set.
819
820 An action/key binding can be selected to change the key binding.
821 """
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400822 reselect = 0
terryjreedye5bb1122017-07-05 00:54:55 -0400823 # XXX - new_keyset isn't used in this function.
terryjreedy938e7382017-06-26 20:48:39 -0400824 new_keyset = 0
825 if self.list_bindings.curselection():
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400826 reselect = 1
terryjreedy938e7382017-06-26 20:48:39 -0400827 list_index = self.list_bindings.index(ANCHOR)
828 keyset = idleConf.GetKeySet(keyset_name)
829 bind_names = list(keyset.keys())
830 bind_names.sort()
831 self.list_bindings.delete(0, END)
832 for bind_name in bind_names:
terryjreedye5bb1122017-07-05 00:54:55 -0400833 key = ' '.join(keyset[bind_name])
834 bind_name = bind_name[2:-2] # Trim off the angle brackets.
terryjreedyedc03422017-07-07 16:37:39 -0400835 if keyset_name in changes['keys']:
terryjreedye5bb1122017-07-05 00:54:55 -0400836 # Handle any unsaved changes to this key set.
terryjreedyedc03422017-07-07 16:37:39 -0400837 if bind_name in changes['keys'][keyset_name]:
838 key = changes['keys'][keyset_name][bind_name]
terryjreedy938e7382017-06-26 20:48:39 -0400839 self.list_bindings.insert(END, bind_name+' - '+key)
Steven M. Gava052937f2002-02-11 02:20:53 +0000840 if reselect:
terryjreedy938e7382017-06-26 20:48:39 -0400841 self.list_bindings.see(list_index)
842 self.list_bindings.select_set(list_index)
843 self.list_bindings.select_anchor(list_index)
Steven M. Gava052937f2002-02-11 02:20:53 +0000844
terryjreedy938e7382017-06-26 20:48:39 -0400845 def delete_custom_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400846 """Handle event to delete a custom key set.
847
848 Applying the delete deactivates the current configuration and
849 reverts to the default. The custom key set is permanently
850 deleted from the config file.
851 """
terryjreedy938e7382017-06-26 20:48:39 -0400852 keyset_name=self.custom_keys.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400853 delmsg = 'Are you sure you wish to delete the key set %r ?'
854 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400855 'Delete Key Set', delmsg % keyset_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000856 return
terryjreedy938e7382017-06-26 20:48:39 -0400857 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400858 # Remove key set from changes, config, and file.
859 changes.remove(keyset_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400860 # Reload user key set list.
terryjreedy938e7382017-06-26 20:48:39 -0400861 item_list = idleConf.GetSectionList('user', 'keys')
862 item_list.sort()
863 if not item_list:
864 self.radio_keys_custom.config(state=DISABLED)
865 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
Steven M. Gava49745752002-02-18 01:43:11 +0000866 else:
terryjreedy938e7382017-06-26 20:48:39 -0400867 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400868 # Revert to default key set.
terryjreedy938e7382017-06-26 20:48:39 -0400869 self.are_keys_builtin.set(idleConf.defaultCfg['main']
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400870 .Get('Keys', 'default'))
terryjreedy938e7382017-06-26 20:48:39 -0400871 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400872 or idleConf.default_keys())
terryjreedye5bb1122017-07-05 00:54:55 -0400873 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400874 changes.save_all()
875 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400876 self.activate_config_changes()
877 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000878
terryjreedy938e7382017-06-26 20:48:39 -0400879 def delete_custom_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400880 """Handle event to delete custom theme.
881
882 The current theme is deactivated and the default theme is
883 activated. The custom theme is permanently removed from
884 the config file.
885 """
terryjreedy938e7382017-06-26 20:48:39 -0400886 theme_name = self.custom_theme.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400887 delmsg = 'Are you sure you wish to delete the theme %r ?'
888 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400889 'Delete Theme', delmsg % theme_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000890 return
terryjreedy938e7382017-06-26 20:48:39 -0400891 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400892 # Remove theme from changes, config, and file.
893 changes.delete_section('highlight')
terryjreedye5bb1122017-07-05 00:54:55 -0400894 # Reload user theme list.
terryjreedy938e7382017-06-26 20:48:39 -0400895 item_list = idleConf.GetSectionList('user', 'highlight')
896 item_list.sort()
897 if not item_list:
898 self.radio_theme_custom.config(state=DISABLED)
899 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
Steven M. Gava49745752002-02-18 01:43:11 +0000900 else:
terryjreedy938e7382017-06-26 20:48:39 -0400901 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400902 # Revert to default theme.
terryjreedy938e7382017-06-26 20:48:39 -0400903 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
904 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
terryjreedye5bb1122017-07-05 00:54:55 -0400905 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400906 changes.save_all()
907 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400908 self.activate_config_changes()
909 self.set_theme_type()
Steven M. Gava49745752002-02-18 01:43:11 +0000910
terryjreedy938e7382017-06-26 20:48:39 -0400911 def get_colour(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400912 """Handle button to select a new color for the target tag.
913
914 If a new color is selected while using a builtin theme, a
915 name must be supplied to create a custom theme.
916 """
terryjreedy938e7382017-06-26 20:48:39 -0400917 target = self.highlight_target.get()
918 prev_colour = self.frame_colour_set.cget('bg')
919 rgbTuplet, colour_string = tkColorChooser.askcolor(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400920 parent=self, title='Pick new colour for : '+target,
terryjreedy938e7382017-06-26 20:48:39 -0400921 initialcolor=prev_colour)
922 if colour_string and (colour_string != prev_colour):
terryjreedye5bb1122017-07-05 00:54:55 -0400923 # User didn't cancel and they chose a new colour.
924 if self.is_builtin_theme.get(): # Current theme is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400925 message = ('Your changes will be saved as a new Custom Theme. '
926 'Enter a name for your new Custom Theme below.')
terryjreedy938e7382017-06-26 20:48:39 -0400927 new_theme = self.get_new_theme_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400928 if not new_theme: # User cancelled custom theme creation.
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000929 return
terryjreedye5bb1122017-07-05 00:54:55 -0400930 else: # Create new custom theme based on previously active theme.
terryjreedy938e7382017-06-26 20:48:39 -0400931 self.create_new_theme(new_theme)
932 self.colour.set(colour_string)
terryjreedye5bb1122017-07-05 00:54:55 -0400933 else: # Current theme is user defined.
terryjreedy938e7382017-06-26 20:48:39 -0400934 self.colour.set(colour_string)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000935
terryjreedy938e7382017-06-26 20:48:39 -0400936 def on_new_colour_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400937 "Display sample of new color selection on the dialog."
terryjreedy938e7382017-06-26 20:48:39 -0400938 new_colour=self.colour.get()
terryjreedye5bb1122017-07-05 00:54:55 -0400939 self.frame_colour_set.config(bg=new_colour) # Set sample.
terryjreedy938e7382017-06-26 20:48:39 -0400940 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
941 sample_element = self.theme_elements[self.highlight_target.get()][0]
942 self.text_highlight_sample.tag_config(sample_element, **{plane:new_colour})
943 theme = self.custom_theme.get()
944 theme_element = sample_element + '-' + plane
terryjreedyedc03422017-07-07 16:37:39 -0400945 changes.add_option('highlight', theme, theme_element, new_colour)
Steven M. Gava052937f2002-02-11 02:20:53 +0000946
terryjreedy938e7382017-06-26 20:48:39 -0400947 def get_new_theme_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400948 "Return name of new theme from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400949 used_names = (idleConf.GetSectionList('user', 'highlight') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400950 idleConf.GetSectionList('default', 'highlight'))
terryjreedy938e7382017-06-26 20:48:39 -0400951 new_theme = SectionName(
952 self, 'New Custom Theme', message, used_names).result
953 return new_theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000954
terryjreedy938e7382017-06-26 20:48:39 -0400955 def save_as_new_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400956 "Prompt for new theme name and create the theme."
terryjreedy938e7382017-06-26 20:48:39 -0400957 new_theme_name = self.get_new_theme_name('New Theme Name:')
958 if new_theme_name:
959 self.create_new_theme(new_theme_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000960
terryjreedy938e7382017-06-26 20:48:39 -0400961 def create_new_theme(self, new_theme_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400962 """Create a new custom theme with the given name.
963
964 Create the new theme based on the previously active theme
965 with the current changes applied. Once it is saved, then
966 activate the new theme.
967 """
terryjreedy938e7382017-06-26 20:48:39 -0400968 if self.is_builtin_theme.get():
969 theme_type = 'default'
970 theme_name = self.builtin_theme.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000971 else:
terryjreedy938e7382017-06-26 20:48:39 -0400972 theme_type = 'user'
973 theme_name = self.custom_theme.get()
974 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400975 # Apply any of the old theme's unsaved changes to the new theme.
terryjreedyedc03422017-07-07 16:37:39 -0400976 if theme_name in changes['highlight']:
977 theme_changes = changes['highlight'][theme_name]
terryjreedy938e7382017-06-26 20:48:39 -0400978 for element in theme_changes:
979 new_theme[element] = theme_changes[element]
terryjreedye5bb1122017-07-05 00:54:55 -0400980 # Save the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400981 self.save_new_theme(new_theme_name, new_theme)
terryjreedye5bb1122017-07-05 00:54:55 -0400982 # Change GUI over to the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400983 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
984 custom_theme_list.sort()
985 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
986 self.is_builtin_theme.set(0)
987 self.set_theme_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000988
terryjreedy938e7382017-06-26 20:48:39 -0400989 def on_list_fonts_button_release(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -0400990 """Handle event of selecting a font from the list.
991
992 Change the font name to the font selected from the list
993 and update sample text to show that font.
994 """
terryjreedy938e7382017-06-26 20:48:39 -0400995 font = self.list_fonts.get(ANCHOR)
996 self.font_name.set(font.lower())
997 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000998
terryjreedy938e7382017-06-26 20:48:39 -0400999 def set_font_sample(self, event=None):
terryjreedye5bb1122017-07-05 00:54:55 -04001000 "Update the screen samples with the font settings from the dialog."
terryjreedy938e7382017-06-26 20:48:39 -04001001 font_name = self.font_name.get()
1002 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
1003 new_font = (font_name, self.font_size.get(), font_weight)
1004 self.font_sample.config(font=new_font)
1005 self.text_highlight_sample.configure(font=new_font)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001006
terryjreedy938e7382017-06-26 20:48:39 -04001007 def set_highlight_target(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001008 "Set fg/bg toggle and color based on highlight tag target."
1009 if self.highlight_target.get() == 'Cursor': # bg not possible
terryjreedy938e7382017-06-26 20:48:39 -04001010 self.radio_fg.config(state=DISABLED)
1011 self.radio_bg.config(state=DISABLED)
1012 self.fg_bg_toggle.set(1)
terryjreedye5bb1122017-07-05 00:54:55 -04001013 else: # Both fg and bg can be set.
terryjreedy938e7382017-06-26 20:48:39 -04001014 self.radio_fg.config(state=NORMAL)
1015 self.radio_bg.config(state=NORMAL)
1016 self.fg_bg_toggle.set(1)
1017 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001018
terryjreedy938e7382017-06-26 20:48:39 -04001019 def set_colour_sample_binding(self, *args):
terryjreedye5bb1122017-07-05 00:54:55 -04001020 "Change color sample based on foreground/background toggle."
terryjreedy938e7382017-06-26 20:48:39 -04001021 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001022
terryjreedy938e7382017-06-26 20:48:39 -04001023 def set_colour_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001024 "Set the color of the frame background to reflect the selected target."
1025 # Set the colour sample area.
terryjreedy938e7382017-06-26 20:48:39 -04001026 tag = self.theme_elements[self.highlight_target.get()][0]
1027 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1028 colour = self.text_highlight_sample.tag_cget(tag, plane)
1029 self.frame_colour_set.config(bg=colour)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001030
terryjreedy938e7382017-06-26 20:48:39 -04001031 def paint_theme_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001032 "Apply the theme colors to each element tag in the sample text."
1033 if self.is_builtin_theme.get(): # Default theme
terryjreedy938e7382017-06-26 20:48:39 -04001034 theme = self.builtin_theme.get()
terryjreedye5bb1122017-07-05 00:54:55 -04001035 else: # User theme
terryjreedy938e7382017-06-26 20:48:39 -04001036 theme = self.custom_theme.get()
1037 for element_title in self.theme_elements:
1038 element = self.theme_elements[element_title][0]
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001039 colours = idleConf.GetHighlight(theme, element)
terryjreedye5bb1122017-07-05 00:54:55 -04001040 if element == 'cursor': # Cursor sample needs special painting.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001041 colours['background'] = idleConf.GetHighlight(
1042 theme, 'normal', fgBg='bg')
terryjreedye5bb1122017-07-05 00:54:55 -04001043 # Handle any unsaved changes to this theme.
terryjreedyedc03422017-07-07 16:37:39 -04001044 if theme in changes['highlight']:
1045 theme_dict = changes['highlight'][theme]
terryjreedy938e7382017-06-26 20:48:39 -04001046 if element + '-foreground' in theme_dict:
1047 colours['foreground'] = theme_dict[element + '-foreground']
1048 if element + '-background' in theme_dict:
1049 colours['background'] = theme_dict[element + '-background']
1050 self.text_highlight_sample.tag_config(element, **colours)
1051 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001052
terryjreedy938e7382017-06-26 20:48:39 -04001053 def help_source_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001054 "Handle event for selecting additional help."
terryjreedy938e7382017-06-26 20:48:39 -04001055 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001056
terryjreedy938e7382017-06-26 20:48:39 -04001057 def set_helplist_button_states(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001058 "Toggle the state for the help list buttons based on list entries."
1059 if self.list_help.size() < 1: # No entries in list.
terryjreedy938e7382017-06-26 20:48:39 -04001060 self.button_helplist_edit.config(state=DISABLED)
1061 self.button_helplist_remove.config(state=DISABLED)
terryjreedye5bb1122017-07-05 00:54:55 -04001062 else: # Some entries.
1063 if self.list_help.curselection(): # There currently is a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001064 self.button_helplist_edit.config(state=NORMAL)
1065 self.button_helplist_remove.config(state=NORMAL)
terryjreedye5bb1122017-07-05 00:54:55 -04001066 else: # There currently is not a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001067 self.button_helplist_edit.config(state=DISABLED)
1068 self.button_helplist_remove.config(state=DISABLED)
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001069
terryjreedy938e7382017-06-26 20:48:39 -04001070 def helplist_item_add(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001071 """Handle add button for the help list.
1072
1073 Query for name and location of new help sources and add
1074 them to the list.
1075 """
terryjreedy938e7382017-06-26 20:48:39 -04001076 help_source = HelpSource(self, 'New Help Source',
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001077 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001078 if help_source:
1079 self.user_helplist.append((help_source[0], help_source[1]))
1080 self.list_help.insert(END, help_source[0])
1081 self.update_user_help_changed_items()
1082 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001083
terryjreedy938e7382017-06-26 20:48:39 -04001084 def helplist_item_edit(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001085 """Handle edit button for the help list.
1086
1087 Query with existing help source information and update
1088 config if the values are changed.
1089 """
terryjreedy938e7382017-06-26 20:48:39 -04001090 item_index = self.list_help.index(ANCHOR)
1091 help_source = self.user_helplist[item_index]
1092 new_help_source = HelpSource(
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001093 self, 'Edit Help Source',
terryjreedy938e7382017-06-26 20:48:39 -04001094 menuitem=help_source[0],
1095 filepath=help_source[1],
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001096 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001097 if new_help_source and new_help_source != help_source:
1098 self.user_helplist[item_index] = new_help_source
1099 self.list_help.delete(item_index)
1100 self.list_help.insert(item_index, new_help_source[0])
1101 self.update_user_help_changed_items()
1102 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001103
terryjreedy938e7382017-06-26 20:48:39 -04001104 def helplist_item_remove(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001105 """Handle remove button for the help list.
1106
1107 Delete the help list item from config.
1108 """
terryjreedy938e7382017-06-26 20:48:39 -04001109 item_index = self.list_help.index(ANCHOR)
1110 del(self.user_helplist[item_index])
1111 self.list_help.delete(item_index)
1112 self.update_user_help_changed_items()
1113 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001114
terryjreedy938e7382017-06-26 20:48:39 -04001115 def update_user_help_changed_items(self):
terryjreedyedc03422017-07-07 16:37:39 -04001116 "Clear and rebuild the HelpFiles section in changes"
1117 changes['main']['HelpFiles'] = {}
terryjreedy938e7382017-06-26 20:48:39 -04001118 for num in range(1, len(self.user_helplist) + 1):
terryjreedyedc03422017-07-07 16:37:39 -04001119 changes.add_option(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001120 'main', 'HelpFiles', str(num),
terryjreedy938e7382017-06-26 20:48:39 -04001121 ';'.join(self.user_helplist[num-1][:2]))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001122
terryjreedy938e7382017-06-26 20:48:39 -04001123 def load_font_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001124 "Load current configuration settings for the font options."
1125 # Set base editor font selection list.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001126 fonts = list(tkFont.families(self))
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001127 fonts.sort()
1128 for font in fonts:
terryjreedy938e7382017-06-26 20:48:39 -04001129 self.list_fonts.insert(END, font)
1130 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
1131 font_name = configured_font[0].lower()
1132 font_size = configured_font[1]
1133 font_bold = configured_font[2]=='bold'
1134 self.font_name.set(font_name)
Kurt B. Kaiser05391692003-05-26 20:35:53 +00001135 lc_fonts = [s.lower() for s in fonts]
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001136 try:
terryjreedy938e7382017-06-26 20:48:39 -04001137 current_font_index = lc_fonts.index(font_name)
1138 self.list_fonts.see(current_font_index)
1139 self.list_fonts.select_set(current_font_index)
1140 self.list_fonts.select_anchor(current_font_index)
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001141 except ValueError:
1142 pass
terryjreedye5bb1122017-07-05 00:54:55 -04001143 # Set font size dropdown.
terryjreedy938e7382017-06-26 20:48:39 -04001144 self.opt_menu_font_size.SetMenu(('7', '8', '9', '10', '11', '12', '13',
Terry Jan Reedyda028872016-08-30 20:19:13 -04001145 '14', '16', '18', '20', '22',
terryjreedy938e7382017-06-26 20:48:39 -04001146 '25', '29', '34', '40'), font_size )
terryjreedye5bb1122017-07-05 00:54:55 -04001147 # Set font weight.
terryjreedy938e7382017-06-26 20:48:39 -04001148 self.font_bold.set(font_bold)
terryjreedye5bb1122017-07-05 00:54:55 -04001149 # Set font sample.
terryjreedy938e7382017-06-26 20:48:39 -04001150 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001151
terryjreedy938e7382017-06-26 20:48:39 -04001152 def load_tab_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001153 "Load current configuration settings for the tab options."
1154 # Set indent sizes.
terryjreedy938e7382017-06-26 20:48:39 -04001155 space_num = idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001156 'main', 'Indent', 'num-spaces', default=4, type='int')
terryjreedy938e7382017-06-26 20:48:39 -04001157 self.space_num.set(space_num)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001158
terryjreedy938e7382017-06-26 20:48:39 -04001159 def load_theme_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001160 "Load current configuration settings for the theme options."
1161 # Set current theme type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001162 self.is_builtin_theme.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001163 'main', 'Theme', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001164 # Set current theme.
terryjreedy938e7382017-06-26 20:48:39 -04001165 current_option = idleConf.CurrentTheme()
terryjreedye5bb1122017-07-05 00:54:55 -04001166 # Load available theme option menus.
1167 if self.is_builtin_theme.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001168 item_list = idleConf.GetSectionList('default', 'highlight')
1169 item_list.sort()
1170 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
1171 item_list = idleConf.GetSectionList('user', 'highlight')
1172 item_list.sort()
1173 if not item_list:
1174 self.radio_theme_custom.config(state=DISABLED)
1175 self.custom_theme.set('- no custom themes -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001176 else:
terryjreedy938e7382017-06-26 20:48:39 -04001177 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001178 else: # User theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001179 item_list = idleConf.GetSectionList('user', 'highlight')
1180 item_list.sort()
1181 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
1182 item_list = idleConf.GetSectionList('default', 'highlight')
1183 item_list.sort()
1184 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
1185 self.set_theme_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001186 # Load theme element option menu.
terryjreedy938e7382017-06-26 20:48:39 -04001187 theme_names = list(self.theme_elements.keys())
1188 theme_names.sort(key=lambda x: self.theme_elements[x][1])
1189 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
1190 self.paint_theme_sample()
1191 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001192
terryjreedy938e7382017-06-26 20:48:39 -04001193 def load_key_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001194 "Load current configuration settings for the keybinding options."
1195 # Set current keys type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001196 self.are_keys_builtin.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001197 'main', 'Keys', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001198 # Set current keys.
terryjreedy938e7382017-06-26 20:48:39 -04001199 current_option = idleConf.CurrentKeys()
terryjreedye5bb1122017-07-05 00:54:55 -04001200 # Load available keyset option menus.
1201 if self.are_keys_builtin.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001202 item_list = idleConf.GetSectionList('default', 'keys')
1203 item_list.sort()
1204 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1205 item_list = idleConf.GetSectionList('user', 'keys')
1206 item_list.sort()
1207 if not item_list:
1208 self.radio_keys_custom.config(state=DISABLED)
1209 self.custom_keys.set('- no custom keys -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001210 else:
terryjreedy938e7382017-06-26 20:48:39 -04001211 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001212 else: # User key set selected.
terryjreedy938e7382017-06-26 20:48:39 -04001213 item_list = idleConf.GetSectionList('user', 'keys')
1214 item_list.sort()
1215 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1216 item_list = idleConf.GetSectionList('default', 'keys')
1217 item_list.sort()
1218 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1219 self.set_keys_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001220 # Load keyset element list.
terryjreedy938e7382017-06-26 20:48:39 -04001221 keyset_name = idleConf.CurrentKeys()
1222 self.load_keys_list(keyset_name)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001223
terryjreedy938e7382017-06-26 20:48:39 -04001224 def load_general_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001225 "Load current configuration settings for the general options."
1226 # Set startup state.
terryjreedy938e7382017-06-26 20:48:39 -04001227 self.startup_edit.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001228 'main', 'General', 'editor-on-startup', default=1, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001229 # Set autosave state.
terryjreedy938e7382017-06-26 20:48:39 -04001230 self.autosave.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001231 'main', 'General', 'autosave', default=0, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001232 # Set initial window size.
terryjreedy938e7382017-06-26 20:48:39 -04001233 self.win_width.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001234 'main', 'EditorWindow', 'width', type='int'))
terryjreedy938e7382017-06-26 20:48:39 -04001235 self.win_height.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001236 'main', 'EditorWindow', 'height', type='int'))
terryjreedye5bb1122017-07-05 00:54:55 -04001237 # Set default source encoding.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001238 self.encoding.set(idleConf.GetOption(
1239 'main', 'EditorWindow', 'encoding', default='none'))
terryjreedye5bb1122017-07-05 00:54:55 -04001240 # Set additional help sources.
terryjreedy938e7382017-06-26 20:48:39 -04001241 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1242 for help_item in self.user_helplist:
1243 self.list_help.insert(END, help_item[0])
1244 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001245
terryjreedy938e7382017-06-26 20:48:39 -04001246 def load_configs(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001247 """Load configuration for each page.
1248
1249 Load configuration from default and user config files and populate
Steven M. Gava429a86af2001-10-23 10:42:12 +00001250 the widgets on the config dialog pages.
1251 """
terryjreedy938e7382017-06-26 20:48:39 -04001252 self.load_font_cfg()
1253 self.load_tab_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001254 self.load_theme_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001255 self.load_key_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001256 self.load_general_cfg()
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001257 # note: extension page handled separately
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001258
terryjreedy938e7382017-06-26 20:48:39 -04001259 def save_new_key_set(self, keyset_name, keyset):
terryjreedye5bb1122017-07-05 00:54:55 -04001260 """Save a newly created core key set.
1261
terryjreedy938e7382017-06-26 20:48:39 -04001262 keyset_name - string, the name of the new key set
1263 keyset - dictionary containing the new key set
Steven M. Gava052937f2002-02-11 02:20:53 +00001264 """
terryjreedy938e7382017-06-26 20:48:39 -04001265 if not idleConf.userCfg['keys'].has_section(keyset_name):
1266 idleConf.userCfg['keys'].add_section(keyset_name)
1267 for event in keyset:
1268 value = keyset[event]
1269 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001270
terryjreedy938e7382017-06-26 20:48:39 -04001271 def save_new_theme(self, theme_name, theme):
terryjreedye5bb1122017-07-05 00:54:55 -04001272 """Save a newly created theme.
1273
terryjreedy938e7382017-06-26 20:48:39 -04001274 theme_name - string, the name of the new theme
Steven M. Gava052937f2002-02-11 02:20:53 +00001275 theme - dictionary containing the new theme
1276 """
terryjreedy938e7382017-06-26 20:48:39 -04001277 if not idleConf.userCfg['highlight'].has_section(theme_name):
1278 idleConf.userCfg['highlight'].add_section(theme_name)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001279 for element in theme:
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001280 value = theme[element]
terryjreedy938e7382017-06-26 20:48:39 -04001281 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001282
terryjreedy938e7382017-06-26 20:48:39 -04001283 def deactivate_current_config(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001284 "Remove current key bindings."
1285 # Before a config is saved, some cleanup of current
1286 # config must be done - remove the previous keybindings.
terryjreedy938e7382017-06-26 20:48:39 -04001287 win_instances = self.parent.instance_dict.keys()
1288 for instance in win_instances:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001289 instance.RemoveKeybindings()
1290
terryjreedy938e7382017-06-26 20:48:39 -04001291 def activate_config_changes(self):
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001292 "Dynamically apply configuration changes"
terryjreedy938e7382017-06-26 20:48:39 -04001293 win_instances = self.parent.instance_dict.keys()
1294 for instance in win_instances:
Steven M. Gavab77d3432002-03-02 07:16:21 +00001295 instance.ResetColorizer()
Steven M. Gavab1585412002-03-12 00:21:56 +00001296 instance.ResetFont()
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001297 instance.set_notabs_indentwidth()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001298 instance.ApplyKeybindings()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +00001299 instance.reset_help_menu_entries()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001300
terryjreedy938e7382017-06-26 20:48:39 -04001301 def cancel(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001302 "Dismiss config dialog."
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001303 self.destroy()
1304
terryjreedy938e7382017-06-26 20:48:39 -04001305 def ok(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001306 "Apply config changes, then dismiss dialog."
terryjreedy938e7382017-06-26 20:48:39 -04001307 self.apply()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001308 self.destroy()
1309
terryjreedy938e7382017-06-26 20:48:39 -04001310 def apply(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001311 "Apply config changes and leave dialog open."
terryjreedy938e7382017-06-26 20:48:39 -04001312 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -04001313 changes.save_all()
1314 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -04001315 self.activate_config_changes()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001316
terryjreedy938e7382017-06-26 20:48:39 -04001317 def help(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001318 "Create textview for config dialog help."
terryjreedy938e7382017-06-26 20:48:39 -04001319 page = self.tab_pages._current_page
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001320 view_text(self, title='Help for IDLE preferences',
1321 text=help_common+help_pages.get(page, ''))
1322
terryjreedy938e7382017-06-26 20:48:39 -04001323 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001324 """Part of the config dialog used for configuring IDLE extensions.
1325
1326 This code is generic - it works for any and all IDLE extensions.
1327
1328 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001329 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001330 GUI interface to change the configuration values, and saves the
1331 changes using idleConf.
1332
1333 Not all changes take effect immediately - some may require restarting IDLE.
1334 This depends on each extension's implementation.
1335
1336 All values are treated as text, and it is up to the user to supply
1337 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001338 which are boolean, and can be toggled with a True/False button.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001339 """
1340 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -04001341 frame = self.tab_pages.pages['Extensions'].frame
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001342 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1343 self.ext_userCfg = idleConf.userCfg['extensions']
1344 self.is_int = self.register(is_int)
1345 self.load_extensions()
terryjreedye5bb1122017-07-05 00:54:55 -04001346 # Create widgets - a listbox shows all available extensions, with the
1347 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001348 self.extension_names = StringVar(self)
1349 frame.rowconfigure(0, weight=1)
1350 frame.columnconfigure(2, weight=1)
1351 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1352 selectmode='browse')
1353 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1354 scroll = Scrollbar(frame, command=self.extension_list.yview)
1355 self.extension_list.yscrollcommand=scroll.set
1356 self.details_frame = LabelFrame(frame, width=250, height=250)
1357 self.extension_list.grid(column=0, row=0, sticky='nws')
1358 scroll.grid(column=1, row=0, sticky='ns')
1359 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1360 frame.configure(padx=10, pady=10)
1361 self.config_frame = {}
1362 self.current_extension = None
1363
1364 self.outerframe = self # TEMPORARY
1365 self.tabbed_page_set = self.extension_list # TEMPORARY
1366
terryjreedye5bb1122017-07-05 00:54:55 -04001367 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001368 ext_names = ''
1369 for ext_name in sorted(self.extensions):
1370 self.create_extension_frame(ext_name)
1371 ext_names = ext_names + '{' + ext_name + '} '
1372 self.extension_names.set(ext_names)
1373 self.extension_list.selection_set(0)
1374 self.extension_selected(None)
1375
1376 def load_extensions(self):
1377 "Fill self.extensions with data from the default and user configs."
1378 self.extensions = {}
1379 for ext_name in idleConf.GetExtensions(active_only=False):
1380 self.extensions[ext_name] = []
1381
1382 for ext_name in self.extensions:
1383 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1384
terryjreedye5bb1122017-07-05 00:54:55 -04001385 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001386 enables = [opt_name for opt_name in opt_list
1387 if opt_name.startswith('enable')]
1388 for opt_name in enables:
1389 opt_list.remove(opt_name)
1390 opt_list = enables + opt_list
1391
1392 for opt_name in opt_list:
1393 def_str = self.ext_defaultCfg.Get(
1394 ext_name, opt_name, raw=True)
1395 try:
1396 def_obj = {'True':True, 'False':False}[def_str]
1397 opt_type = 'bool'
1398 except KeyError:
1399 try:
1400 def_obj = int(def_str)
1401 opt_type = 'int'
1402 except ValueError:
1403 def_obj = def_str
1404 opt_type = None
1405 try:
1406 value = self.ext_userCfg.Get(
1407 ext_name, opt_name, type=opt_type, raw=True,
1408 default=def_obj)
terryjreedye5bb1122017-07-05 00:54:55 -04001409 except ValueError: # Need this until .Get fixed.
1410 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001411 var = StringVar(self)
1412 var.set(str(value))
1413
1414 self.extensions[ext_name].append({'name': opt_name,
1415 'type': opt_type,
1416 'default': def_str,
1417 'value': value,
1418 'var': var,
1419 })
1420
1421 def extension_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001422 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001423 newsel = self.extension_list.curselection()
1424 if newsel:
1425 newsel = self.extension_list.get(newsel)
1426 if newsel is None or newsel != self.current_extension:
1427 if self.current_extension:
1428 self.details_frame.config(text='')
1429 self.config_frame[self.current_extension].grid_forget()
1430 self.current_extension = None
1431 if newsel:
1432 self.details_frame.config(text=newsel)
1433 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1434 self.current_extension = newsel
1435
1436 def create_extension_frame(self, ext_name):
1437 """Create a frame holding the widgets to configure one extension"""
1438 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1439 self.config_frame[ext_name] = f
1440 entry_area = f.interior
terryjreedye5bb1122017-07-05 00:54:55 -04001441 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001442 for row, opt in enumerate(self.extensions[ext_name]):
terryjreedye5bb1122017-07-05 00:54:55 -04001443 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001444 label = Label(entry_area, text=opt['name'])
1445 label.grid(row=row, column=0, sticky=NW)
1446 var = opt['var']
1447 if opt['type'] == 'bool':
1448 Checkbutton(entry_area, textvariable=var, variable=var,
1449 onvalue='True', offvalue='False',
1450 indicatoron=FALSE, selectcolor='', width=8
1451 ).grid(row=row, column=1, sticky=W, padx=7)
1452 elif opt['type'] == 'int':
1453 Entry(entry_area, textvariable=var, validate='key',
1454 validatecommand=(self.is_int, '%P')
1455 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1456
1457 else:
1458 Entry(entry_area, textvariable=var
1459 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1460 return
1461
1462 def set_extension_value(self, section, opt):
terryjreedye5bb1122017-07-05 00:54:55 -04001463 """Return True if the configuration was added or changed.
1464
1465 If the value is the same as the default, then remove it
1466 from user config file.
1467 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001468 name = opt['name']
1469 default = opt['default']
1470 value = opt['var'].get().strip() or default
1471 opt['var'].set(value)
1472 # if self.defaultCfg.has_section(section):
terryjreedye5bb1122017-07-05 00:54:55 -04001473 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001474 if (value == default):
1475 return self.ext_userCfg.RemoveOption(section, name)
terryjreedye5bb1122017-07-05 00:54:55 -04001476 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001477 return self.ext_userCfg.SetOption(section, name, value)
1478
1479 def save_all_changed_extensions(self):
1480 """Save configuration changes to the user config file."""
1481 has_changes = False
1482 for ext_name in self.extensions:
1483 options = self.extensions[ext_name]
1484 for opt in options:
1485 if self.set_extension_value(ext_name, opt):
1486 has_changes = True
1487 if has_changes:
1488 self.ext_userCfg.Save()
1489
1490
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001491help_common = '''\
1492When you click either the Apply or Ok buttons, settings in this
1493dialog that are different from IDLE's default are saved in
1494a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001495these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001496machine. Some do not take affect until IDLE is restarted.
1497[Cancel] only cancels changes made since the last save.
1498'''
1499help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001500 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001501Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001502The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001503be used with older IDLE releases if it is saved as a custom
1504theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001505''',
1506 'Keys': '''
1507Keys:
1508The IDLE Modern Unix key set is new in June 2016. It can only
1509be used with older IDLE releases if it is saved as a custom
1510key set, with a different name.
1511''',
terryjreedyaf683822017-06-27 23:02:19 -04001512 'Extensions': '''
1513Extensions:
1514
1515Autocomplete: Popupwait is milleseconds to wait after key char, without
1516cursor movement, before popping up completion box. Key char is '.' after
1517identifier or a '/' (or '\\' on Windows) within a string.
1518
1519FormatParagraph: Max-width is max chars in lines after re-formatting.
1520Use with paragraphs in both strings and comment blocks.
1521
1522ParenMatch: Style indicates what is highlighted when closer is entered:
1523'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1524'expression' (default) - also everything in between. Flash-delay is how
1525long to highlight if cursor is not moved (0 means forever).
1526'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001527}
1528
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001529
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001530def is_int(s):
1531 "Return 's is blank or represents an int'"
1532 if not s:
1533 return True
1534 try:
1535 int(s)
1536 return True
1537 except ValueError:
1538 return False
1539
1540
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001541class VerticalScrolledFrame(Frame):
1542 """A pure Tkinter vertically scrollable frame.
1543
1544 * Use the 'interior' attribute to place widgets inside the scrollable frame
1545 * Construct and pack/place/grid normally
1546 * This frame only allows vertical scrolling
1547 """
1548 def __init__(self, parent, *args, **kw):
1549 Frame.__init__(self, parent, *args, **kw)
1550
terryjreedye5bb1122017-07-05 00:54:55 -04001551 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001552 vscrollbar = Scrollbar(self, orient=VERTICAL)
1553 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1554 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001555 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001556 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1557 vscrollbar.config(command=canvas.yview)
1558
terryjreedye5bb1122017-07-05 00:54:55 -04001559 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001560 canvas.xview_moveto(0)
1561 canvas.yview_moveto(0)
1562
terryjreedye5bb1122017-07-05 00:54:55 -04001563 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001564 self.interior = interior = Frame(canvas)
1565 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1566
terryjreedye5bb1122017-07-05 00:54:55 -04001567 # Track changes to the canvas and frame width and sync them,
1568 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001569 def _configure_interior(event):
terryjreedye5bb1122017-07-05 00:54:55 -04001570 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001571 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1572 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001573 interior.bind('<Configure>', _configure_interior)
1574
1575 def _configure_canvas(event):
1576 if interior.winfo_reqwidth() != canvas.winfo_width():
terryjreedye5bb1122017-07-05 00:54:55 -04001577 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001578 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1579 canvas.bind('<Configure>', _configure_canvas)
1580
1581 return
1582
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001583
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00001584if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04001585 import unittest
1586 unittest.main('idlelib.idle_test.test_configdialog',
1587 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04001588 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04001589 run(ConfigDialog)