blob: 1e1561f7457310fe65cf43c942e94e73a6c6915b [file] [log] [blame]
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +00001"""IDLE Configuration Dialog: support user customization of IDLE by GUI
2
3Customize font faces, sizes, and colorization attributes. Set indentation
4defaults. Customize keybindings. Colorization and keybindings can be
5saved as user defined sets. Select startup options including shell/editor
6and default window size. Define additional help sources.
7
8Note that tab width in IDLE is currently fixed at eight due to Tk issues.
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00009Refer to comments in EditorWindow autoindent code for details.
Kurt B. Kaisere7a161e2003-01-10 20:13:57 +000010
Steven M. Gava44d3d1a2001-07-31 06:59:02 +000011"""
terryjreedy938e7382017-06-26 20:48:39 -040012from tkinter import (Toplevel, Frame, LabelFrame, Listbox, Label, Button,
13 Entry, Text, Scale, Radiobutton, Checkbutton, Canvas,
14 StringVar, BooleanVar, IntVar, TRUE, FALSE,
15 TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE, NORMAL, DISABLED,
16 NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
terryjreedy7ab33422017-07-09 19:26:32 -040017 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
Terry Jan 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)
terryjreedy7ab33422017-07-09 19:26:32 -040081 self.fontlist.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
terryjreedy7ab33422017-07-09 19:26:32 -0400146 # Create widgets.
147 # body and body section frames.
terryjreedy938e7382017-06-26 20:48:39 -0400148 frame = self.tab_pages.pages['Fonts/Tabs'].frame
terryjreedy938e7382017-06-26 20:48:39 -0400149 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400150 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
terryjreedy938e7382017-06-26 20:48:39 -0400151 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400152 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
terryjreedy7ab33422017-07-09 19:26:32 -0400153 # frame_font
terryjreedy938e7382017-06-26 20:48:39 -0400154 frame_font_name = Frame(frame_font)
155 frame_font_param = Frame(frame_font)
156 font_name_title = Label(
157 frame_font_name, justify=LEFT, text='Font Face :')
terryjreedy7ab33422017-07-09 19:26:32 -0400158 self.fontlist = Listbox(
terryjreedy938e7382017-06-26 20:48:39 -0400159 frame_font_name, height=5, takefocus=FALSE, exportselection=FALSE)
terryjreedy7ab33422017-07-09 19:26:32 -0400160 self.fontlist.bind('<<ListboxSelect>>', self.on_fontlist_select)
terryjreedy938e7382017-06-26 20:48:39 -0400161 scroll_font = Scrollbar(frame_font_name)
terryjreedy7ab33422017-07-09 19:26:32 -0400162 scroll_font.config(command=self.fontlist.yview)
163 self.fontlist.config(yscrollcommand=scroll_font.set)
terryjreedy938e7382017-06-26 20:48:39 -0400164 font_size_title = Label(frame_font_param, text='Size :')
165 self.opt_menu_font_size = DynOptionMenu(
166 frame_font_param, self.font_size, None, command=self.set_font_sample)
167 check_font_bold = Checkbutton(
168 frame_font_param, variable=self.font_bold, onvalue=1,
169 offvalue=0, text='Bold', command=self.set_font_sample)
170 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
171 self.font_sample = Label(
172 frame_font_sample, justify=LEFT, font=self.edit_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400173 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
terryjreedy7ab33422017-07-09 19:26:32 -0400174 # frame_indent
terryjreedy938e7382017-06-26 20:48:39 -0400175 frame_indent_size = Frame(frame_indent)
176 indent_size_title = Label(
177 frame_indent_size, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400178 text='Python Standard: 4 Spaces!')
terryjreedy938e7382017-06-26 20:48:39 -0400179 self.scale_indent_size = Scale(
180 frame_indent_size, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400181 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400182
terryjreedy7ab33422017-07-09 19:26:32 -0400183 # Pack widgets.
184 # body
terryjreedy938e7382017-06-26 20:48:39 -0400185 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
186 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
terryjreedy7ab33422017-07-09 19:26:32 -0400187 # frame_font
terryjreedy938e7382017-06-26 20:48:39 -0400188 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
189 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
190 font_name_title.pack(side=TOP, anchor=W)
terryjreedy7ab33422017-07-09 19:26:32 -0400191 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
terryjreedy938e7382017-06-26 20:48:39 -0400192 scroll_font.pack(side=LEFT, fill=Y)
193 font_size_title.pack(side=LEFT, anchor=W)
194 self.opt_menu_font_size.pack(side=LEFT, anchor=W)
195 check_font_bold.pack(side=LEFT, anchor=W, padx=20)
196 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
197 self.font_sample.pack(expand=TRUE, fill=BOTH)
terryjreedy7ab33422017-07-09 19:26:32 -0400198 # frame_indent
terryjreedy938e7382017-06-26 20:48:39 -0400199 frame_indent_size.pack(side=TOP, fill=X)
200 indent_size_title.pack(side=TOP, anchor=W, padx=5)
201 self.scale_indent_size.pack(side=TOP, padx=5, fill=X)
terryjreedy7ab33422017-07-09 19:26:32 -0400202
Steven M. Gava952d0a52001-08-03 04:43:44 +0000203 return frame
204
terryjreedy938e7382017-06-26 20:48:39 -0400205 def create_page_highlight(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400206 """Return frame of widgets for Highlighting tab.
207
208 Configuration attributes:
209 builtin_theme: Menu variable for built-in theme.
210 custom_theme: Menu variable for custom theme.
211 fg_bg_toggle: Toggle for foreground/background color.
212 colour: Color of selected target.
213 is_builtin_theme: Selector for built-in or custom theme.
214 highlight_target: Menu variable for the highlight tag target.
215 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400216 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400217 self.builtin_theme = StringVar(parent)
218 self.custom_theme = StringVar(parent)
219 self.fg_bg_toggle = BooleanVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400220 self.colour = StringVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400221 # XXX - font_name is defined in create_page_font_tab. Needed here too?
terryjreedy938e7382017-06-26 20:48:39 -0400222 self.font_name = StringVar(parent)
223 self.is_builtin_theme = BooleanVar(parent)
224 self.highlight_target = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400225
Steven M. Gava952d0a52001-08-03 04:43:44 +0000226 ##widget creation
227 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400228 frame = self.tab_pages.pages['Highlighting'].frame
Steven M. Gava952d0a52001-08-03 04:43:44 +0000229 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400230 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400231 text=' Custom Highlighting ')
terryjreedy938e7382017-06-26 20:48:39 -0400232 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400233 text=' Highlighting Theme ')
terryjreedy938e7382017-06-26 20:48:39 -0400234 #frame_custom
235 self.text_highlight_sample=Text(
236 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400237 font=('courier', 12, ''), cursor='hand2', width=21, height=11,
238 takefocus=FALSE, highlightthickness=0, wrap=NONE)
terryjreedy938e7382017-06-26 20:48:39 -0400239 text=self.text_highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400240 text.bind('<Double-Button-1>', lambda e: 'break')
241 text.bind('<B1-Motion>', lambda e: 'break')
terryjreedy938e7382017-06-26 20:48:39 -0400242 text_and_tags=(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400243 ('#you can click here', 'comment'), ('\n', 'normal'),
244 ('#to choose items', 'comment'), ('\n', 'normal'),
245 ('def', 'keyword'), (' ', 'normal'),
246 ('func', 'definition'), ('(param):\n ', 'normal'),
247 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
248 ("'string'", 'string'), ('\n var1 = ', 'normal'),
249 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
250 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
251 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400252 ('None', 'keyword'), (')\n', 'normal'),
253 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400254 (' error ', 'error'), (' ', 'normal'),
255 ('cursor |', 'cursor'), ('\n ', 'normal'),
256 ('shell', 'console'), (' ', 'normal'),
257 ('stdout', 'stdout'), (' ', 'normal'),
258 ('stderr', 'stderr'), ('\n', 'normal'))
terryjreedy938e7382017-06-26 20:48:39 -0400259 for texttag in text_and_tags:
260 text.insert(END, texttag[0], texttag[1])
261 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400262 def tem(event, elem=element):
terryjreedy938e7382017-06-26 20:48:39 -0400263 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400264 text.tag_bind(
terryjreedy938e7382017-06-26 20:48:39 -0400265 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Steven M. Gavae16d94b2001-11-03 05:07:28 +0000266 text.config(state=DISABLED)
terryjreedy938e7382017-06-26 20:48:39 -0400267 self.frame_colour_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
268 frame_fg_bg_toggle = Frame(frame_custom)
269 button_set_colour = Button(
270 self.frame_colour_set, text='Choose Colour for :',
271 command=self.get_colour, highlightthickness=0)
272 self.opt_menu_highlight_target = DynOptionMenu(
273 self.frame_colour_set, self.highlight_target, None,
274 highlightthickness=0) #, command=self.set_highlight_targetBinding
275 self.radio_fg = Radiobutton(
276 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
277 text='Foreground', command=self.set_colour_sample_binding)
278 self.radio_bg=Radiobutton(
279 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
280 text='Background', command=self.set_colour_sample_binding)
281 self.fg_bg_toggle.set(1)
282 button_save_custom_theme = Button(
283 frame_custom, text='Save as New Custom Theme',
284 command=self.save_as_new_theme)
285 #frame_theme
286 theme_type_title = Label(frame_theme, text='Select : ')
287 self.radio_theme_builtin = Radiobutton(
288 frame_theme, variable=self.is_builtin_theme, value=1,
289 command=self.set_theme_type, text='a Built-in Theme')
290 self.radio_theme_custom = Radiobutton(
291 frame_theme, variable=self.is_builtin_theme, value=0,
292 command=self.set_theme_type, text='a Custom Theme')
293 self.opt_menu_theme_builtin = DynOptionMenu(
294 frame_theme, self.builtin_theme, None, command=None)
295 self.opt_menu_theme_custom=DynOptionMenu(
296 frame_theme, self.custom_theme, None, command=None)
297 self.button_delete_custom_theme=Button(
298 frame_theme, text='Delete Custom Theme',
299 command=self.delete_custom_theme)
300 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400301
Steven M. Gava952d0a52001-08-03 04:43:44 +0000302 ##widget packing
303 #body
terryjreedy938e7382017-06-26 20:48:39 -0400304 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
305 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
306 #frame_custom
307 self.frame_colour_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
308 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
309 self.text_highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400310 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
terryjreedy938e7382017-06-26 20:48:39 -0400311 button_set_colour.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
312 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400313 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
terryjreedy938e7382017-06-26 20:48:39 -0400314 self.radio_fg.pack(side=LEFT, anchor=E)
315 self.radio_bg.pack(side=RIGHT, anchor=W)
316 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
317 #frame_theme
318 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
319 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
320 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
321 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
322 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
323 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500324 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000325 return frame
326
terryjreedy938e7382017-06-26 20:48:39 -0400327 def create_page_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400328 """Return frame of widgets for Keys tab.
329
330 Configuration attributes:
331 builtin_keys: Menu variable for built-in keybindings.
332 custom_keys: Menu variable for custom keybindings.
333 are_keys_builtin: Selector for built-in or custom keybindings.
334 keybinding: Action/key bindings.
335 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400336 parent = self.parent
terryjreedye5bb1122017-07-05 00:54:55 -0400337 # XXX - binding_target isn't used.
terryjreedy938e7382017-06-26 20:48:39 -0400338 self.binding_target = StringVar(parent)
339 self.builtin_keys = StringVar(parent)
340 self.custom_keys = StringVar(parent)
341 self.are_keys_builtin = BooleanVar(parent)
342 self.keybinding = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400343
Steven M. Gava60fc7072001-08-04 13:58:22 +0000344 ##widget creation
345 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400346 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +0000347 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400348 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400349 frame, borderwidth=2, relief=GROOVE,
350 text=' Custom Key Bindings ')
terryjreedy938e7382017-06-26 20:48:39 -0400351 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400352 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
terryjreedy938e7382017-06-26 20:48:39 -0400353 #frame_custom
354 frame_target = Frame(frame_custom)
355 target_title = Label(frame_target, text='Action - Key(s)')
356 scroll_target_y = Scrollbar(frame_target)
357 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
358 self.list_bindings = Listbox(
359 frame_target, takefocus=FALSE, exportselection=FALSE)
360 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
361 scroll_target_y.config(command=self.list_bindings.yview)
362 scroll_target_x.config(command=self.list_bindings.xview)
363 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
364 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
365 self.button_new_keys = Button(
366 frame_custom, text='Get New Keys for Selection',
367 command=self.get_new_keys, state=DISABLED)
368 #frame_key_sets
369 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +0000370 for i in range(2)]
terryjreedy938e7382017-06-26 20:48:39 -0400371 self.radio_keys_builtin = Radiobutton(
372 frames[0], variable=self.are_keys_builtin, value=1,
373 command=self.set_keys_type, text='Use a Built-in Key Set')
374 self.radio_keys_custom = Radiobutton(
375 frames[0], variable=self.are_keys_builtin, value=0,
376 command=self.set_keys_type, text='Use a Custom Key Set')
377 self.opt_menu_keys_builtin = DynOptionMenu(
378 frames[0], self.builtin_keys, None, command=None)
379 self.opt_menu_keys_custom = DynOptionMenu(
380 frames[0], self.custom_keys, None, command=None)
381 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400382 frames[1], text='Delete Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400383 command=self.delete_custom_keys)
384 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400385 frames[1], text='Save as New Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400386 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400387 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400388
Steven M. Gava60fc7072001-08-04 13:58:22 +0000389 ##widget packing
390 #body
terryjreedy938e7382017-06-26 20:48:39 -0400391 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
392 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
393 #frame_custom
394 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
395 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +0000396 #frame target
terryjreedy938e7382017-06-26 20:48:39 -0400397 frame_target.columnconfigure(0, weight=1)
398 frame_target.rowconfigure(1, weight=1)
399 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
400 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
401 scroll_target_y.grid(row=1, column=1, sticky=NS)
402 scroll_target_x.grid(row=2, column=0, sticky=EW)
403 #frame_key_sets
404 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
405 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
406 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
407 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400408 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
terryjreedy938e7382017-06-26 20:48:39 -0400409 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
410 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +0000411 frames[0].pack(side=TOP, fill=BOTH, expand=True)
412 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000413 return frame
414
terryjreedy938e7382017-06-26 20:48:39 -0400415 def create_page_general(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400416 """Return frame of widgets for General tab.
417
418 Configuration attributes:
419 win_width: Initial window width in characters.
420 win_height: Initial window height in characters.
421 startup_edit: Selector for opening in editor or shell mode.
422 autosave: Selector for save prompt popup when using Run.
423 encoding: ?
424 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400425 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400426 self.win_width = StringVar(parent)
427 self.win_height = StringVar(parent)
428 self.startup_edit = IntVar(parent)
429 self.autosave = IntVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400430 # XXX - encoding isn't on the screen to be set, but is saved to config.
Terry Jan Reedy22405332014-07-30 19:24:32 -0400431 self.encoding = StringVar(parent)
terryjreedye5bb1122017-07-05 00:54:55 -0400432 # XXX - user_help_browser and help_browser aren't used.
terryjreedy938e7382017-06-26 20:48:39 -0400433 self.user_help_browser = BooleanVar(parent)
434 self.help_browser = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400435
Steven M. Gava230e5782001-08-07 03:28:25 +0000436 #widget creation
437 #body
terryjreedy938e7382017-06-26 20:48:39 -0400438 frame = self.tab_pages.pages['General'].frame
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000439 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400440 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400441 text=' Startup Preferences ')
terryjreedy938e7382017-06-26 20:48:39 -0400442 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
443 text=' autosave Preferences ')
444 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
445 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400446 text=' Additional Help Sources ')
terryjreedy938e7382017-06-26 20:48:39 -0400447 #frame_run
448 startup_title = Label(frame_run, text='At Startup')
449 self.radio_startup_edit = Radiobutton(
450 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500451 text="Open Edit Window")
terryjreedy938e7382017-06-26 20:48:39 -0400452 self.radio_startup_shell = Radiobutton(
453 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500454 text='Open Shell Window')
terryjreedy938e7382017-06-26 20:48:39 -0400455 #frame_save
456 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
457 self.radio_save_ask = Radiobutton(
458 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500459 text="Prompt to Save")
terryjreedy938e7382017-06-26 20:48:39 -0400460 self.radio_save_auto = Radiobutton(
461 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500462 text='No Prompt')
terryjreedy938e7382017-06-26 20:48:39 -0400463 #frame_win_size
464 win_size_title = Label(
465 frame_win_size, text='Initial Window Size (in characters)')
466 win_width_title = Label(frame_win_size, text='Width')
467 self.entry_win_width = Entry(
468 frame_win_size, textvariable=self.win_width, width=3)
469 win_height_title = Label(frame_win_size, text='Height')
470 self.entry_win_height = Entry(
471 frame_win_size, textvariable=self.win_height, width=3)
472 #frame_help
473 frame_helplist = Frame(frame_help)
474 frame_helplist_buttons = Frame(frame_helplist)
475 scroll_helplist = Scrollbar(frame_helplist)
476 self.list_help = Listbox(
477 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000478 exportselection=FALSE)
terryjreedy938e7382017-06-26 20:48:39 -0400479 scroll_helplist.config(command=self.list_help.yview)
480 self.list_help.config(yscrollcommand=scroll_helplist.set)
481 self.list_help.bind('<ButtonRelease-1>', self.help_source_selected)
482 self.button_helplist_edit = Button(
483 frame_helplist_buttons, text='Edit', state=DISABLED,
484 width=8, command=self.helplist_item_edit)
485 self.button_helplist_add = Button(
486 frame_helplist_buttons, text='Add',
487 width=8, command=self.helplist_item_add)
488 self.button_helplist_remove = Button(
489 frame_helplist_buttons, text='Remove', state=DISABLED,
490 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400491
Steven M. Gava230e5782001-08-07 03:28:25 +0000492 #widget packing
493 #body
terryjreedy938e7382017-06-26 20:48:39 -0400494 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
495 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
496 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
497 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
498 #frame_run
499 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
500 self.radio_startup_shell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
501 self.radio_startup_edit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
502 #frame_save
503 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
504 self.radio_save_auto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
505 self.radio_save_ask.pack(side=RIGHT, anchor=W, padx=5, pady=5)
506 #frame_win_size
507 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
508 self.entry_win_height.pack(side=RIGHT, anchor=E, padx=10, pady=5)
509 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
510 self.entry_win_width.pack(side=RIGHT, anchor=E, padx=10, pady=5)
511 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
512 #frame_help
513 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
514 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
515 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
516 self.list_help.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
517 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
518 self.button_helplist_add.pack(side=TOP, anchor=W)
519 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000520 return frame
521
terryjreedy938e7382017-06-26 20:48:39 -0400522 def attach_var_callbacks(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400523 "Attach callbacks to variables that can be changed."
terryjreedy938e7382017-06-26 20:48:39 -0400524 self.font_size.trace_add('write', self.var_changed_font)
525 self.font_name.trace_add('write', self.var_changed_font)
526 self.font_bold.trace_add('write', self.var_changed_font)
527 self.space_num.trace_add('write', self.var_changed_space_num)
528 self.colour.trace_add('write', self.var_changed_colour)
529 self.builtin_theme.trace_add('write', self.var_changed_builtin_theme)
530 self.custom_theme.trace_add('write', self.var_changed_custom_theme)
531 self.is_builtin_theme.trace_add('write', self.var_changed_is_builtin_theme)
532 self.highlight_target.trace_add('write', self.var_changed_highlight_target)
533 self.keybinding.trace_add('write', self.var_changed_keybinding)
534 self.builtin_keys.trace_add('write', self.var_changed_builtin_keys)
535 self.custom_keys.trace_add('write', self.var_changed_custom_keys)
536 self.are_keys_builtin.trace_add('write', self.var_changed_are_keys_builtin)
537 self.win_width.trace_add('write', self.var_changed_win_width)
538 self.win_height.trace_add('write', self.var_changed_win_height)
539 self.startup_edit.trace_add('write', self.var_changed_startup_edit)
540 self.autosave.trace_add('write', self.var_changed_autosave)
541 self.encoding.trace_add('write', self.var_changed_encoding)
Steven M. Gava052937f2002-02-11 02:20:53 +0000542
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400543 def remove_var_callbacks(self):
544 "Remove callbacks to prevent memory leaks."
545 for var in (
terryjreedy938e7382017-06-26 20:48:39 -0400546 self.font_size, self.font_name, self.font_bold,
547 self.space_num, self.colour, self.builtin_theme,
548 self.custom_theme, self.is_builtin_theme, self.highlight_target,
549 self.keybinding, self.builtin_keys, self.custom_keys,
550 self.are_keys_builtin, self.win_width, self.win_height,
551 self.startup_edit, self.autosave, self.encoding,):
Serhiy Storchaka81221742016-06-26 09:46:57 +0300552 var.trace_remove('write', var.trace_info()[0][1])
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400553
terryjreedy938e7382017-06-26 20:48:39 -0400554 def var_changed_font(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400555 """Store changes to font attributes.
556
557 When one font attribute changes, save them all, as they are
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400558 not independent from each other. In particular, when we are
559 overriding the default font, we need to write out everything.
terryjreedye5bb1122017-07-05 00:54:55 -0400560 """
terryjreedy938e7382017-06-26 20:48:39 -0400561 value = self.font_name.get()
terryjreedyedc03422017-07-07 16:37:39 -0400562 changes.add_option('main', 'EditorWindow', 'font', value)
terryjreedy938e7382017-06-26 20:48:39 -0400563 value = self.font_size.get()
terryjreedyedc03422017-07-07 16:37:39 -0400564 changes.add_option('main', 'EditorWindow', 'font-size', value)
terryjreedy938e7382017-06-26 20:48:39 -0400565 value = self.font_bold.get()
terryjreedyedc03422017-07-07 16:37:39 -0400566 changes.add_option('main', 'EditorWindow', 'font-bold', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000567
terryjreedy938e7382017-06-26 20:48:39 -0400568 def var_changed_space_num(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400569 "Store change to indentation size."
terryjreedy938e7382017-06-26 20:48:39 -0400570 value = self.space_num.get()
terryjreedyedc03422017-07-07 16:37:39 -0400571 changes.add_option('main', 'Indent', 'num-spaces', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000572
terryjreedy938e7382017-06-26 20:48:39 -0400573 def var_changed_colour(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400574 "Process change to color choice."
terryjreedy938e7382017-06-26 20:48:39 -0400575 self.on_new_colour_set()
Steven M. Gava052937f2002-02-11 02:20:53 +0000576
terryjreedy938e7382017-06-26 20:48:39 -0400577 def var_changed_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400578 """Process new builtin theme selection.
579
580 Add the changed theme's name to the changed_items and recreate
581 the sample with the values from the selected theme.
582 """
terryjreedy938e7382017-06-26 20:48:39 -0400583 old_themes = ('IDLE Classic', 'IDLE New')
584 value = self.builtin_theme.get()
585 if value not in old_themes:
586 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
terryjreedyedc03422017-07-07 16:37:39 -0400587 changes.add_option('main', 'Theme', 'name', old_themes[0])
588 changes.add_option('main', 'Theme', 'name2', value)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500589 self.new_custom_theme.config(text='New theme, see Help',
590 fg='#500000')
591 else:
terryjreedyedc03422017-07-07 16:37:39 -0400592 changes.add_option('main', 'Theme', 'name', value)
593 changes.add_option('main', 'Theme', 'name2', '')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500594 self.new_custom_theme.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400595 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000596
terryjreedy938e7382017-06-26 20:48:39 -0400597 def var_changed_custom_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400598 """Process new custom theme selection.
599
600 If a new custom theme is selected, add the name to the
601 changed_items and apply the theme to the sample.
602 """
terryjreedy938e7382017-06-26 20:48:39 -0400603 value = self.custom_theme.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000604 if value != '- no custom themes -':
terryjreedyedc03422017-07-07 16:37:39 -0400605 changes.add_option('main', 'Theme', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400606 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000607
terryjreedy938e7382017-06-26 20:48:39 -0400608 def var_changed_is_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400609 """Process toggle between builtin and custom theme.
610
611 Update the default toggle value and apply the newly
612 selected theme type.
613 """
terryjreedy938e7382017-06-26 20:48:39 -0400614 value = self.is_builtin_theme.get()
terryjreedyedc03422017-07-07 16:37:39 -0400615 changes.add_option('main', 'Theme', 'default', value)
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000616 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400617 self.var_changed_builtin_theme()
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000618 else:
terryjreedy938e7382017-06-26 20:48:39 -0400619 self.var_changed_custom_theme()
Steven M. Gava052937f2002-02-11 02:20:53 +0000620
terryjreedy938e7382017-06-26 20:48:39 -0400621 def var_changed_highlight_target(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400622 "Process selection of new target tag for highlighting."
terryjreedy938e7382017-06-26 20:48:39 -0400623 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000624
terryjreedy938e7382017-06-26 20:48:39 -0400625 def var_changed_keybinding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400626 "Store change to a keybinding."
terryjreedy938e7382017-06-26 20:48:39 -0400627 value = self.keybinding.get()
628 key_set = self.custom_keys.get()
629 event = self.list_bindings.get(ANCHOR).split()[0]
Steven M. Gavaa498af22002-02-01 01:33:36 +0000630 if idleConf.IsCoreBinding(event):
terryjreedyedc03422017-07-07 16:37:39 -0400631 changes.add_option('keys', key_set, event, value)
terryjreedye5bb1122017-07-05 00:54:55 -0400632 else: # Event is an extension binding.
terryjreedy938e7382017-06-26 20:48:39 -0400633 ext_name = idleConf.GetExtnNameForEvent(event)
634 ext_keybind_section = ext_name + '_cfgBindings'
terryjreedyedc03422017-07-07 16:37:39 -0400635 changes.add_option('extensions', ext_keybind_section, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000636
terryjreedy938e7382017-06-26 20:48:39 -0400637 def var_changed_builtin_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400638 "Process selection of builtin key set."
terryjreedy938e7382017-06-26 20:48:39 -0400639 old_keys = (
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400640 'IDLE Classic Windows',
641 'IDLE Classic Unix',
642 'IDLE Classic Mac',
643 'IDLE Classic OSX',
644 )
terryjreedy938e7382017-06-26 20:48:39 -0400645 value = self.builtin_keys.get()
646 if value not in old_keys:
647 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
terryjreedyedc03422017-07-07 16:37:39 -0400648 changes.add_option('main', 'Keys', 'name', old_keys[0])
649 changes.add_option('main', 'Keys', 'name2', value)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400650 self.new_custom_keys.config(text='New key set, see Help',
651 fg='#500000')
652 else:
terryjreedyedc03422017-07-07 16:37:39 -0400653 changes.add_option('main', 'Keys', 'name', value)
654 changes.add_option('main', 'Keys', 'name2', '')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400655 self.new_custom_keys.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400656 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000657
terryjreedy938e7382017-06-26 20:48:39 -0400658 def var_changed_custom_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400659 "Process selection of custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400660 value = self.custom_keys.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000661 if value != '- no custom keys -':
terryjreedyedc03422017-07-07 16:37:39 -0400662 changes.add_option('main', 'Keys', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400663 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000664
terryjreedy938e7382017-06-26 20:48:39 -0400665 def var_changed_are_keys_builtin(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400666 "Process toggle between builtin key set and custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400667 value = self.are_keys_builtin.get()
terryjreedyedc03422017-07-07 16:37:39 -0400668 changes.add_option('main', 'Keys', 'default', value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000669 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400670 self.var_changed_builtin_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000671 else:
terryjreedy938e7382017-06-26 20:48:39 -0400672 self.var_changed_custom_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000673
terryjreedy938e7382017-06-26 20:48:39 -0400674 def var_changed_win_width(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400675 "Store change to window width."
terryjreedy938e7382017-06-26 20:48:39 -0400676 value = self.win_width.get()
terryjreedyedc03422017-07-07 16:37:39 -0400677 changes.add_option('main', 'EditorWindow', 'width', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000678
terryjreedy938e7382017-06-26 20:48:39 -0400679 def var_changed_win_height(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400680 "Store change to window height."
terryjreedy938e7382017-06-26 20:48:39 -0400681 value = self.win_height.get()
terryjreedyedc03422017-07-07 16:37:39 -0400682 changes.add_option('main', 'EditorWindow', 'height', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000683
terryjreedy938e7382017-06-26 20:48:39 -0400684 def var_changed_startup_edit(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400685 "Store change to toggle for starting IDLE in the editor or shell."
terryjreedy938e7382017-06-26 20:48:39 -0400686 value = self.startup_edit.get()
terryjreedyedc03422017-07-07 16:37:39 -0400687 changes.add_option('main', 'General', 'editor-on-startup', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000688
terryjreedy938e7382017-06-26 20:48:39 -0400689 def var_changed_autosave(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400690 "Store change to autosave."
terryjreedy938e7382017-06-26 20:48:39 -0400691 value = self.autosave.get()
terryjreedyedc03422017-07-07 16:37:39 -0400692 changes.add_option('main', 'General', 'autosave', value)
Kurt B. Kaiser6c638b62003-05-26 06:23:10 +0000693
terryjreedy938e7382017-06-26 20:48:39 -0400694 def var_changed_encoding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400695 "Store change to encoding."
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400696 value = self.encoding.get()
terryjreedyedc03422017-07-07 16:37:39 -0400697 changes.add_option('main', 'EditorWindow', 'encoding', value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000698
terryjreedy938e7382017-06-26 20:48:39 -0400699 def set_theme_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400700 "Set available screen options based on builtin or custom theme."
terryjreedy938e7382017-06-26 20:48:39 -0400701 if self.is_builtin_theme.get():
702 self.opt_menu_theme_builtin.config(state=NORMAL)
703 self.opt_menu_theme_custom.config(state=DISABLED)
704 self.button_delete_custom_theme.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000705 else:
terryjreedy938e7382017-06-26 20:48:39 -0400706 self.opt_menu_theme_builtin.config(state=DISABLED)
707 self.radio_theme_custom.config(state=NORMAL)
708 self.opt_menu_theme_custom.config(state=NORMAL)
709 self.button_delete_custom_theme.config(state=NORMAL)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000710
terryjreedy938e7382017-06-26 20:48:39 -0400711 def set_keys_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400712 "Set available screen options based on builtin or custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400713 if self.are_keys_builtin.get():
714 self.opt_menu_keys_builtin.config(state=NORMAL)
715 self.opt_menu_keys_custom.config(state=DISABLED)
716 self.button_delete_custom_keys.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000717 else:
terryjreedy938e7382017-06-26 20:48:39 -0400718 self.opt_menu_keys_builtin.config(state=DISABLED)
719 self.radio_keys_custom.config(state=NORMAL)
720 self.opt_menu_keys_custom.config(state=NORMAL)
721 self.button_delete_custom_keys.config(state=NORMAL)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000722
terryjreedy938e7382017-06-26 20:48:39 -0400723 def get_new_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400724 """Handle event to change key binding for selected line.
725
726 A selection of a key/binding in the list of current
727 bindings pops up a dialog to enter a new binding. If
728 the current key set is builtin and a binding has
729 changed, then a name for a custom key set needs to be
730 entered for the change to be applied.
731 """
terryjreedy938e7382017-06-26 20:48:39 -0400732 list_index = self.list_bindings.index(ANCHOR)
733 binding = self.list_bindings.get(list_index)
terryjreedye5bb1122017-07-05 00:54:55 -0400734 bind_name = binding.split()[0]
terryjreedy938e7382017-06-26 20:48:39 -0400735 if self.are_keys_builtin.get():
736 current_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000737 else:
terryjreedy938e7382017-06-26 20:48:39 -0400738 current_key_set_name = self.custom_keys.get()
739 current_bindings = idleConf.GetCurrentKeySet()
terryjreedyedc03422017-07-07 16:37:39 -0400740 if current_key_set_name in changes['keys']: # unsaved changes
741 key_set_changes = changes['keys'][current_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400742 for event in key_set_changes:
743 current_bindings[event] = key_set_changes[event].split()
744 current_key_sequences = list(current_bindings.values())
745 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
746 current_key_sequences).result
terryjreedye5bb1122017-07-05 00:54:55 -0400747 if new_keys:
748 if self.are_keys_builtin.get(): # Current key set is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400749 message = ('Your changes will be saved as a new Custom Key Set.'
750 ' Enter a name for your new Custom Key Set below.')
terryjreedy938e7382017-06-26 20:48:39 -0400751 new_keyset = self.get_new_keys_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400752 if not new_keyset: # User cancelled custom key set creation.
terryjreedy938e7382017-06-26 20:48:39 -0400753 self.list_bindings.select_set(list_index)
754 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000755 return
terryjreedye5bb1122017-07-05 00:54:55 -0400756 else: # Create new custom key set based on previously active key set.
terryjreedy938e7382017-06-26 20:48:39 -0400757 self.create_new_key_set(new_keyset)
758 self.list_bindings.delete(list_index)
759 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
760 self.list_bindings.select_set(list_index)
761 self.list_bindings.select_anchor(list_index)
762 self.keybinding.set(new_keys)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000763 else:
terryjreedy938e7382017-06-26 20:48:39 -0400764 self.list_bindings.select_set(list_index)
765 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000766
terryjreedy938e7382017-06-26 20:48:39 -0400767 def get_new_keys_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400768 "Return new key set name from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400769 used_names = (idleConf.GetSectionList('user', 'keys') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400770 idleConf.GetSectionList('default', 'keys'))
terryjreedy938e7382017-06-26 20:48:39 -0400771 new_keyset = SectionName(
772 self, 'New Custom Key Set', message, used_names).result
773 return new_keyset
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000774
terryjreedy938e7382017-06-26 20:48:39 -0400775 def save_as_new_key_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400776 "Prompt for name of new key set and save changes using that name."
terryjreedy938e7382017-06-26 20:48:39 -0400777 new_keys_name = self.get_new_keys_name('New Key Set Name:')
778 if new_keys_name:
779 self.create_new_key_set(new_keys_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000780
terryjreedy938e7382017-06-26 20:48:39 -0400781 def keybinding_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -0400782 "Activate button to assign new keys to selected action."
terryjreedy938e7382017-06-26 20:48:39 -0400783 self.button_new_keys.config(state=NORMAL)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000784
terryjreedy938e7382017-06-26 20:48:39 -0400785 def create_new_key_set(self, new_key_set_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400786 """Create a new custom key set with the given name.
787
788 Create the new key set based on the previously active set
789 with the current changes applied. Once it is saved, then
790 activate the new key set.
791 """
terryjreedy938e7382017-06-26 20:48:39 -0400792 if self.are_keys_builtin.get():
793 prev_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000794 else:
terryjreedy938e7382017-06-26 20:48:39 -0400795 prev_key_set_name = self.custom_keys.get()
796 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
797 new_keys = {}
terryjreedye5bb1122017-07-05 00:54:55 -0400798 for event in prev_keys: # Add key set to changed items.
799 event_name = event[2:-2] # Trim off the angle brackets.
terryjreedy938e7382017-06-26 20:48:39 -0400800 binding = ' '.join(prev_keys[event])
801 new_keys[event_name] = binding
terryjreedye5bb1122017-07-05 00:54:55 -0400802 # Handle any unsaved changes to prev key set.
terryjreedyedc03422017-07-07 16:37:39 -0400803 if prev_key_set_name in changes['keys']:
804 key_set_changes = changes['keys'][prev_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400805 for event in key_set_changes:
806 new_keys[event] = key_set_changes[event]
terryjreedye5bb1122017-07-05 00:54:55 -0400807 # Save the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400808 self.save_new_key_set(new_key_set_name, new_keys)
terryjreedye5bb1122017-07-05 00:54:55 -0400809 # Change GUI over to the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400810 custom_key_list = idleConf.GetSectionList('user', 'keys')
811 custom_key_list.sort()
812 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
813 self.are_keys_builtin.set(0)
814 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000815
terryjreedy938e7382017-06-26 20:48:39 -0400816 def load_keys_list(self, keyset_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400817 """Reload the list of action/key binding pairs for the active key set.
818
819 An action/key binding can be selected to change the key binding.
820 """
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400821 reselect = 0
terryjreedye5bb1122017-07-05 00:54:55 -0400822 # XXX - new_keyset isn't used in this function.
terryjreedy938e7382017-06-26 20:48:39 -0400823 new_keyset = 0
824 if self.list_bindings.curselection():
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400825 reselect = 1
terryjreedy938e7382017-06-26 20:48:39 -0400826 list_index = self.list_bindings.index(ANCHOR)
827 keyset = idleConf.GetKeySet(keyset_name)
828 bind_names = list(keyset.keys())
829 bind_names.sort()
830 self.list_bindings.delete(0, END)
831 for bind_name in bind_names:
terryjreedye5bb1122017-07-05 00:54:55 -0400832 key = ' '.join(keyset[bind_name])
833 bind_name = bind_name[2:-2] # Trim off the angle brackets.
terryjreedyedc03422017-07-07 16:37:39 -0400834 if keyset_name in changes['keys']:
terryjreedye5bb1122017-07-05 00:54:55 -0400835 # Handle any unsaved changes to this key set.
terryjreedyedc03422017-07-07 16:37:39 -0400836 if bind_name in changes['keys'][keyset_name]:
837 key = changes['keys'][keyset_name][bind_name]
terryjreedy938e7382017-06-26 20:48:39 -0400838 self.list_bindings.insert(END, bind_name+' - '+key)
Steven M. Gava052937f2002-02-11 02:20:53 +0000839 if reselect:
terryjreedy938e7382017-06-26 20:48:39 -0400840 self.list_bindings.see(list_index)
841 self.list_bindings.select_set(list_index)
842 self.list_bindings.select_anchor(list_index)
Steven M. Gava052937f2002-02-11 02:20:53 +0000843
terryjreedy938e7382017-06-26 20:48:39 -0400844 def delete_custom_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400845 """Handle event to delete a custom key set.
846
847 Applying the delete deactivates the current configuration and
848 reverts to the default. The custom key set is permanently
849 deleted from the config file.
850 """
terryjreedy938e7382017-06-26 20:48:39 -0400851 keyset_name=self.custom_keys.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400852 delmsg = 'Are you sure you wish to delete the key set %r ?'
853 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400854 'Delete Key Set', delmsg % keyset_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000855 return
terryjreedy938e7382017-06-26 20:48:39 -0400856 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400857 # Remove key set from changes, config, and file.
858 changes.remove(keyset_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400859 # Reload user key set list.
terryjreedy938e7382017-06-26 20:48:39 -0400860 item_list = idleConf.GetSectionList('user', 'keys')
861 item_list.sort()
862 if not item_list:
863 self.radio_keys_custom.config(state=DISABLED)
864 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
Steven M. Gava49745752002-02-18 01:43:11 +0000865 else:
terryjreedy938e7382017-06-26 20:48:39 -0400866 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400867 # Revert to default key set.
terryjreedy938e7382017-06-26 20:48:39 -0400868 self.are_keys_builtin.set(idleConf.defaultCfg['main']
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400869 .Get('Keys', 'default'))
terryjreedy938e7382017-06-26 20:48:39 -0400870 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400871 or idleConf.default_keys())
terryjreedye5bb1122017-07-05 00:54:55 -0400872 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400873 changes.save_all()
874 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400875 self.activate_config_changes()
876 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000877
terryjreedy938e7382017-06-26 20:48:39 -0400878 def delete_custom_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400879 """Handle event to delete custom theme.
880
881 The current theme is deactivated and the default theme is
882 activated. The custom theme is permanently removed from
883 the config file.
884 """
terryjreedy938e7382017-06-26 20:48:39 -0400885 theme_name = self.custom_theme.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400886 delmsg = 'Are you sure you wish to delete the theme %r ?'
887 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400888 'Delete Theme', delmsg % theme_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000889 return
terryjreedy938e7382017-06-26 20:48:39 -0400890 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400891 # Remove theme from changes, config, and file.
892 changes.delete_section('highlight')
terryjreedye5bb1122017-07-05 00:54:55 -0400893 # Reload user theme list.
terryjreedy938e7382017-06-26 20:48:39 -0400894 item_list = idleConf.GetSectionList('user', 'highlight')
895 item_list.sort()
896 if not item_list:
897 self.radio_theme_custom.config(state=DISABLED)
898 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
Steven M. Gava49745752002-02-18 01:43:11 +0000899 else:
terryjreedy938e7382017-06-26 20:48:39 -0400900 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400901 # Revert to default theme.
terryjreedy938e7382017-06-26 20:48:39 -0400902 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
903 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
terryjreedye5bb1122017-07-05 00:54:55 -0400904 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400905 changes.save_all()
906 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400907 self.activate_config_changes()
908 self.set_theme_type()
Steven M. Gava49745752002-02-18 01:43:11 +0000909
terryjreedy938e7382017-06-26 20:48:39 -0400910 def get_colour(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400911 """Handle button to select a new color for the target tag.
912
913 If a new color is selected while using a builtin theme, a
914 name must be supplied to create a custom theme.
915 """
terryjreedy938e7382017-06-26 20:48:39 -0400916 target = self.highlight_target.get()
917 prev_colour = self.frame_colour_set.cget('bg')
918 rgbTuplet, colour_string = tkColorChooser.askcolor(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400919 parent=self, title='Pick new colour for : '+target,
terryjreedy938e7382017-06-26 20:48:39 -0400920 initialcolor=prev_colour)
921 if colour_string and (colour_string != prev_colour):
terryjreedye5bb1122017-07-05 00:54:55 -0400922 # User didn't cancel and they chose a new colour.
923 if self.is_builtin_theme.get(): # Current theme is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400924 message = ('Your changes will be saved as a new Custom Theme. '
925 'Enter a name for your new Custom Theme below.')
terryjreedy938e7382017-06-26 20:48:39 -0400926 new_theme = self.get_new_theme_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400927 if not new_theme: # User cancelled custom theme creation.
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000928 return
terryjreedye5bb1122017-07-05 00:54:55 -0400929 else: # Create new custom theme based on previously active theme.
terryjreedy938e7382017-06-26 20:48:39 -0400930 self.create_new_theme(new_theme)
931 self.colour.set(colour_string)
terryjreedye5bb1122017-07-05 00:54:55 -0400932 else: # Current theme is user defined.
terryjreedy938e7382017-06-26 20:48:39 -0400933 self.colour.set(colour_string)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000934
terryjreedy938e7382017-06-26 20:48:39 -0400935 def on_new_colour_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400936 "Display sample of new color selection on the dialog."
terryjreedy938e7382017-06-26 20:48:39 -0400937 new_colour=self.colour.get()
terryjreedye5bb1122017-07-05 00:54:55 -0400938 self.frame_colour_set.config(bg=new_colour) # Set sample.
terryjreedy938e7382017-06-26 20:48:39 -0400939 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
940 sample_element = self.theme_elements[self.highlight_target.get()][0]
941 self.text_highlight_sample.tag_config(sample_element, **{plane:new_colour})
942 theme = self.custom_theme.get()
943 theme_element = sample_element + '-' + plane
terryjreedyedc03422017-07-07 16:37:39 -0400944 changes.add_option('highlight', theme, theme_element, new_colour)
Steven M. Gava052937f2002-02-11 02:20:53 +0000945
terryjreedy938e7382017-06-26 20:48:39 -0400946 def get_new_theme_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400947 "Return name of new theme from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400948 used_names = (idleConf.GetSectionList('user', 'highlight') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400949 idleConf.GetSectionList('default', 'highlight'))
terryjreedy938e7382017-06-26 20:48:39 -0400950 new_theme = SectionName(
951 self, 'New Custom Theme', message, used_names).result
952 return new_theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000953
terryjreedy938e7382017-06-26 20:48:39 -0400954 def save_as_new_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400955 "Prompt for new theme name and create the theme."
terryjreedy938e7382017-06-26 20:48:39 -0400956 new_theme_name = self.get_new_theme_name('New Theme Name:')
957 if new_theme_name:
958 self.create_new_theme(new_theme_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000959
terryjreedy938e7382017-06-26 20:48:39 -0400960 def create_new_theme(self, new_theme_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400961 """Create a new custom theme with the given name.
962
963 Create the new theme based on the previously active theme
964 with the current changes applied. Once it is saved, then
965 activate the new theme.
966 """
terryjreedy938e7382017-06-26 20:48:39 -0400967 if self.is_builtin_theme.get():
968 theme_type = 'default'
969 theme_name = self.builtin_theme.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000970 else:
terryjreedy938e7382017-06-26 20:48:39 -0400971 theme_type = 'user'
972 theme_name = self.custom_theme.get()
973 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400974 # Apply any of the old theme's unsaved changes to the new theme.
terryjreedyedc03422017-07-07 16:37:39 -0400975 if theme_name in changes['highlight']:
976 theme_changes = changes['highlight'][theme_name]
terryjreedy938e7382017-06-26 20:48:39 -0400977 for element in theme_changes:
978 new_theme[element] = theme_changes[element]
terryjreedye5bb1122017-07-05 00:54:55 -0400979 # Save the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400980 self.save_new_theme(new_theme_name, new_theme)
terryjreedye5bb1122017-07-05 00:54:55 -0400981 # Change GUI over to the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400982 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
983 custom_theme_list.sort()
984 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
985 self.is_builtin_theme.set(0)
986 self.set_theme_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000987
terryjreedy7ab33422017-07-09 19:26:32 -0400988 def on_fontlist_select(self, event):
989 """Handle selecting a font from the list.
terryjreedye5bb1122017-07-05 00:54:55 -0400990
terryjreedy7ab33422017-07-09 19:26:32 -0400991 Event can result from either mouse click or Up or Down key.
992 Set font_name and example display to selection.
terryjreedye5bb1122017-07-05 00:54:55 -0400993 """
terryjreedy7ab33422017-07-09 19:26:32 -0400994 font = self.fontlist.get(ANCHOR if event.type == 3 else ACTIVE)
terryjreedy938e7382017-06-26 20:48:39 -0400995 self.font_name.set(font.lower())
996 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000997
terryjreedy938e7382017-06-26 20:48:39 -0400998 def set_font_sample(self, event=None):
terryjreedye5bb1122017-07-05 00:54:55 -0400999 "Update the screen samples with the font settings from the dialog."
terryjreedy938e7382017-06-26 20:48:39 -04001000 font_name = self.font_name.get()
1001 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
1002 new_font = (font_name, self.font_size.get(), font_weight)
1003 self.font_sample.config(font=new_font)
1004 self.text_highlight_sample.configure(font=new_font)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001005
terryjreedy938e7382017-06-26 20:48:39 -04001006 def set_highlight_target(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001007 "Set fg/bg toggle and color based on highlight tag target."
1008 if self.highlight_target.get() == 'Cursor': # bg not possible
terryjreedy938e7382017-06-26 20:48:39 -04001009 self.radio_fg.config(state=DISABLED)
1010 self.radio_bg.config(state=DISABLED)
1011 self.fg_bg_toggle.set(1)
terryjreedye5bb1122017-07-05 00:54:55 -04001012 else: # Both fg and bg can be set.
terryjreedy938e7382017-06-26 20:48:39 -04001013 self.radio_fg.config(state=NORMAL)
1014 self.radio_bg.config(state=NORMAL)
1015 self.fg_bg_toggle.set(1)
1016 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001017
terryjreedy938e7382017-06-26 20:48:39 -04001018 def set_colour_sample_binding(self, *args):
terryjreedye5bb1122017-07-05 00:54:55 -04001019 "Change color sample based on foreground/background toggle."
terryjreedy938e7382017-06-26 20:48:39 -04001020 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001021
terryjreedy938e7382017-06-26 20:48:39 -04001022 def set_colour_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001023 "Set the color of the frame background to reflect the selected target."
1024 # Set the colour sample area.
terryjreedy938e7382017-06-26 20:48:39 -04001025 tag = self.theme_elements[self.highlight_target.get()][0]
1026 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1027 colour = self.text_highlight_sample.tag_cget(tag, plane)
1028 self.frame_colour_set.config(bg=colour)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001029
terryjreedy938e7382017-06-26 20:48:39 -04001030 def paint_theme_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001031 "Apply the theme colors to each element tag in the sample text."
1032 if self.is_builtin_theme.get(): # Default theme
terryjreedy938e7382017-06-26 20:48:39 -04001033 theme = self.builtin_theme.get()
terryjreedye5bb1122017-07-05 00:54:55 -04001034 else: # User theme
terryjreedy938e7382017-06-26 20:48:39 -04001035 theme = self.custom_theme.get()
1036 for element_title in self.theme_elements:
1037 element = self.theme_elements[element_title][0]
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001038 colours = idleConf.GetHighlight(theme, element)
terryjreedye5bb1122017-07-05 00:54:55 -04001039 if element == 'cursor': # Cursor sample needs special painting.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001040 colours['background'] = idleConf.GetHighlight(
1041 theme, 'normal', fgBg='bg')
terryjreedye5bb1122017-07-05 00:54:55 -04001042 # Handle any unsaved changes to this theme.
terryjreedyedc03422017-07-07 16:37:39 -04001043 if theme in changes['highlight']:
1044 theme_dict = changes['highlight'][theme]
terryjreedy938e7382017-06-26 20:48:39 -04001045 if element + '-foreground' in theme_dict:
1046 colours['foreground'] = theme_dict[element + '-foreground']
1047 if element + '-background' in theme_dict:
1048 colours['background'] = theme_dict[element + '-background']
1049 self.text_highlight_sample.tag_config(element, **colours)
1050 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001051
terryjreedy938e7382017-06-26 20:48:39 -04001052 def help_source_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001053 "Handle event for selecting additional help."
terryjreedy938e7382017-06-26 20:48:39 -04001054 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001055
terryjreedy938e7382017-06-26 20:48:39 -04001056 def set_helplist_button_states(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001057 "Toggle the state for the help list buttons based on list entries."
1058 if self.list_help.size() < 1: # No entries in list.
terryjreedy938e7382017-06-26 20:48:39 -04001059 self.button_helplist_edit.config(state=DISABLED)
1060 self.button_helplist_remove.config(state=DISABLED)
terryjreedye5bb1122017-07-05 00:54:55 -04001061 else: # Some entries.
1062 if self.list_help.curselection(): # There currently is a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001063 self.button_helplist_edit.config(state=NORMAL)
1064 self.button_helplist_remove.config(state=NORMAL)
terryjreedye5bb1122017-07-05 00:54:55 -04001065 else: # There currently is not a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001066 self.button_helplist_edit.config(state=DISABLED)
1067 self.button_helplist_remove.config(state=DISABLED)
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001068
terryjreedy938e7382017-06-26 20:48:39 -04001069 def helplist_item_add(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001070 """Handle add button for the help list.
1071
1072 Query for name and location of new help sources and add
1073 them to the list.
1074 """
terryjreedy938e7382017-06-26 20:48:39 -04001075 help_source = HelpSource(self, 'New Help Source',
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001076 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001077 if help_source:
1078 self.user_helplist.append((help_source[0], help_source[1]))
1079 self.list_help.insert(END, help_source[0])
1080 self.update_user_help_changed_items()
1081 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001082
terryjreedy938e7382017-06-26 20:48:39 -04001083 def helplist_item_edit(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001084 """Handle edit button for the help list.
1085
1086 Query with existing help source information and update
1087 config if the values are changed.
1088 """
terryjreedy938e7382017-06-26 20:48:39 -04001089 item_index = self.list_help.index(ANCHOR)
1090 help_source = self.user_helplist[item_index]
1091 new_help_source = HelpSource(
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001092 self, 'Edit Help Source',
terryjreedy938e7382017-06-26 20:48:39 -04001093 menuitem=help_source[0],
1094 filepath=help_source[1],
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001095 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001096 if new_help_source and new_help_source != help_source:
1097 self.user_helplist[item_index] = new_help_source
1098 self.list_help.delete(item_index)
1099 self.list_help.insert(item_index, new_help_source[0])
1100 self.update_user_help_changed_items()
1101 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001102
terryjreedy938e7382017-06-26 20:48:39 -04001103 def helplist_item_remove(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001104 """Handle remove button for the help list.
1105
1106 Delete the help list item from config.
1107 """
terryjreedy938e7382017-06-26 20:48:39 -04001108 item_index = self.list_help.index(ANCHOR)
1109 del(self.user_helplist[item_index])
1110 self.list_help.delete(item_index)
1111 self.update_user_help_changed_items()
1112 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001113
terryjreedy938e7382017-06-26 20:48:39 -04001114 def update_user_help_changed_items(self):
terryjreedyedc03422017-07-07 16:37:39 -04001115 "Clear and rebuild the HelpFiles section in changes"
1116 changes['main']['HelpFiles'] = {}
terryjreedy938e7382017-06-26 20:48:39 -04001117 for num in range(1, len(self.user_helplist) + 1):
terryjreedyedc03422017-07-07 16:37:39 -04001118 changes.add_option(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001119 'main', 'HelpFiles', str(num),
terryjreedy938e7382017-06-26 20:48:39 -04001120 ';'.join(self.user_helplist[num-1][:2]))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001121
terryjreedy938e7382017-06-26 20:48:39 -04001122 def load_font_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001123 "Load current configuration settings for the font options."
1124 # Set base editor font selection list.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001125 fonts = list(tkFont.families(self))
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001126 fonts.sort()
1127 for font in fonts:
terryjreedy7ab33422017-07-09 19:26:32 -04001128 self.fontlist.insert(END, font)
terryjreedy938e7382017-06-26 20:48:39 -04001129 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
1130 font_name = configured_font[0].lower()
1131 font_size = configured_font[1]
1132 font_bold = configured_font[2]=='bold'
1133 self.font_name.set(font_name)
Kurt B. Kaiser05391692003-05-26 20:35:53 +00001134 lc_fonts = [s.lower() for s in fonts]
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001135 try:
terryjreedy938e7382017-06-26 20:48:39 -04001136 current_font_index = lc_fonts.index(font_name)
terryjreedy7ab33422017-07-09 19:26:32 -04001137 self.fontlist.see(current_font_index)
1138 self.fontlist.select_set(current_font_index)
1139 self.fontlist.select_anchor(current_font_index)
1140 self.fontlist.activate(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)