blob: a2cfaab0d0acbe9b5610d461d23924cddc2c73da [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)
terryjreedy953e5272017-07-11 02:16:41 -0400160 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
161 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
162 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
terryjreedy938e7382017-06-26 20:48:39 -0400163 scroll_font = Scrollbar(frame_font_name)
terryjreedy7ab33422017-07-09 19:26:32 -0400164 scroll_font.config(command=self.fontlist.yview)
165 self.fontlist.config(yscrollcommand=scroll_font.set)
terryjreedy938e7382017-06-26 20:48:39 -0400166 font_size_title = Label(frame_font_param, text='Size :')
167 self.opt_menu_font_size = DynOptionMenu(
168 frame_font_param, self.font_size, None, command=self.set_font_sample)
169 check_font_bold = Checkbutton(
170 frame_font_param, variable=self.font_bold, onvalue=1,
171 offvalue=0, text='Bold', command=self.set_font_sample)
172 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
173 self.font_sample = Label(
174 frame_font_sample, justify=LEFT, font=self.edit_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400175 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
terryjreedy7ab33422017-07-09 19:26:32 -0400176 # frame_indent
terryjreedy938e7382017-06-26 20:48:39 -0400177 frame_indent_size = Frame(frame_indent)
178 indent_size_title = Label(
179 frame_indent_size, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400180 text='Python Standard: 4 Spaces!')
terryjreedy938e7382017-06-26 20:48:39 -0400181 self.scale_indent_size = Scale(
182 frame_indent_size, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400183 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400184
terryjreedy7ab33422017-07-09 19:26:32 -0400185 # Pack widgets.
186 # body
terryjreedy938e7382017-06-26 20:48:39 -0400187 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
188 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
terryjreedy7ab33422017-07-09 19:26:32 -0400189 # frame_font
terryjreedy938e7382017-06-26 20:48:39 -0400190 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
191 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
192 font_name_title.pack(side=TOP, anchor=W)
terryjreedy7ab33422017-07-09 19:26:32 -0400193 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
terryjreedy938e7382017-06-26 20:48:39 -0400194 scroll_font.pack(side=LEFT, fill=Y)
195 font_size_title.pack(side=LEFT, anchor=W)
196 self.opt_menu_font_size.pack(side=LEFT, anchor=W)
197 check_font_bold.pack(side=LEFT, anchor=W, padx=20)
198 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
199 self.font_sample.pack(expand=TRUE, fill=BOTH)
terryjreedy7ab33422017-07-09 19:26:32 -0400200 # frame_indent
terryjreedy938e7382017-06-26 20:48:39 -0400201 frame_indent_size.pack(side=TOP, fill=X)
202 indent_size_title.pack(side=TOP, anchor=W, padx=5)
203 self.scale_indent_size.pack(side=TOP, padx=5, fill=X)
terryjreedy7ab33422017-07-09 19:26:32 -0400204
Steven M. Gava952d0a52001-08-03 04:43:44 +0000205 return frame
206
terryjreedy938e7382017-06-26 20:48:39 -0400207 def create_page_highlight(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400208 """Return frame of widgets for Highlighting tab.
209
210 Configuration attributes:
211 builtin_theme: Menu variable for built-in theme.
212 custom_theme: Menu variable for custom theme.
213 fg_bg_toggle: Toggle for foreground/background color.
214 colour: Color of selected target.
215 is_builtin_theme: Selector for built-in or custom theme.
216 highlight_target: Menu variable for the highlight tag target.
217 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400218 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400219 self.builtin_theme = StringVar(parent)
220 self.custom_theme = StringVar(parent)
221 self.fg_bg_toggle = BooleanVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400222 self.colour = StringVar(parent)
terryjreedy938e7382017-06-26 20:48:39 -0400223 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
terryjreedy938e7382017-06-26 20:48:39 -0400337 self.builtin_keys = StringVar(parent)
338 self.custom_keys = StringVar(parent)
339 self.are_keys_builtin = BooleanVar(parent)
340 self.keybinding = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400341
Steven M. Gava60fc7072001-08-04 13:58:22 +0000342 ##widget creation
343 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400344 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +0000345 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400346 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400347 frame, borderwidth=2, relief=GROOVE,
348 text=' Custom Key Bindings ')
terryjreedy938e7382017-06-26 20:48:39 -0400349 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400350 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
terryjreedy938e7382017-06-26 20:48:39 -0400351 #frame_custom
352 frame_target = Frame(frame_custom)
353 target_title = Label(frame_target, text='Action - Key(s)')
354 scroll_target_y = Scrollbar(frame_target)
355 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
356 self.list_bindings = Listbox(
357 frame_target, takefocus=FALSE, exportselection=FALSE)
358 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
359 scroll_target_y.config(command=self.list_bindings.yview)
360 scroll_target_x.config(command=self.list_bindings.xview)
361 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
362 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
363 self.button_new_keys = Button(
364 frame_custom, text='Get New Keys for Selection',
365 command=self.get_new_keys, state=DISABLED)
366 #frame_key_sets
367 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +0000368 for i in range(2)]
terryjreedy938e7382017-06-26 20:48:39 -0400369 self.radio_keys_builtin = Radiobutton(
370 frames[0], variable=self.are_keys_builtin, value=1,
371 command=self.set_keys_type, text='Use a Built-in Key Set')
372 self.radio_keys_custom = Radiobutton(
373 frames[0], variable=self.are_keys_builtin, value=0,
374 command=self.set_keys_type, text='Use a Custom Key Set')
375 self.opt_menu_keys_builtin = DynOptionMenu(
376 frames[0], self.builtin_keys, None, command=None)
377 self.opt_menu_keys_custom = DynOptionMenu(
378 frames[0], self.custom_keys, None, command=None)
379 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400380 frames[1], text='Delete Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400381 command=self.delete_custom_keys)
382 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400383 frames[1], text='Save as New Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400384 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400385 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400386
Steven M. Gava60fc7072001-08-04 13:58:22 +0000387 ##widget packing
388 #body
terryjreedy938e7382017-06-26 20:48:39 -0400389 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
390 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
391 #frame_custom
392 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
393 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +0000394 #frame target
terryjreedy938e7382017-06-26 20:48:39 -0400395 frame_target.columnconfigure(0, weight=1)
396 frame_target.rowconfigure(1, weight=1)
397 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
398 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
399 scroll_target_y.grid(row=1, column=1, sticky=NS)
400 scroll_target_x.grid(row=2, column=0, sticky=EW)
401 #frame_key_sets
402 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
403 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
404 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
405 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400406 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
terryjreedy938e7382017-06-26 20:48:39 -0400407 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
408 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +0000409 frames[0].pack(side=TOP, fill=BOTH, expand=True)
410 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000411 return frame
412
terryjreedy938e7382017-06-26 20:48:39 -0400413 def create_page_general(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400414 """Return frame of widgets for General tab.
415
416 Configuration attributes:
417 win_width: Initial window width in characters.
418 win_height: Initial window height in characters.
419 startup_edit: Selector for opening in editor or shell mode.
420 autosave: Selector for save prompt popup when using Run.
terryjreedye5bb1122017-07-05 00:54:55 -0400421 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400422 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400423 self.win_width = StringVar(parent)
424 self.win_height = StringVar(parent)
425 self.startup_edit = IntVar(parent)
426 self.autosave = IntVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400427
Steven M. Gava230e5782001-08-07 03:28:25 +0000428 #widget creation
429 #body
terryjreedy938e7382017-06-26 20:48:39 -0400430 frame = self.tab_pages.pages['General'].frame
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000431 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400432 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400433 text=' Startup Preferences ')
terryjreedy938e7382017-06-26 20:48:39 -0400434 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
435 text=' autosave Preferences ')
436 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
437 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400438 text=' Additional Help Sources ')
terryjreedy938e7382017-06-26 20:48:39 -0400439 #frame_run
440 startup_title = Label(frame_run, text='At Startup')
441 self.radio_startup_edit = Radiobutton(
442 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500443 text="Open Edit Window")
terryjreedy938e7382017-06-26 20:48:39 -0400444 self.radio_startup_shell = Radiobutton(
445 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500446 text='Open Shell Window')
terryjreedy938e7382017-06-26 20:48:39 -0400447 #frame_save
448 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
449 self.radio_save_ask = Radiobutton(
450 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500451 text="Prompt to Save")
terryjreedy938e7382017-06-26 20:48:39 -0400452 self.radio_save_auto = Radiobutton(
453 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500454 text='No Prompt')
terryjreedy938e7382017-06-26 20:48:39 -0400455 #frame_win_size
456 win_size_title = Label(
457 frame_win_size, text='Initial Window Size (in characters)')
458 win_width_title = Label(frame_win_size, text='Width')
459 self.entry_win_width = Entry(
460 frame_win_size, textvariable=self.win_width, width=3)
461 win_height_title = Label(frame_win_size, text='Height')
462 self.entry_win_height = Entry(
463 frame_win_size, textvariable=self.win_height, width=3)
464 #frame_help
465 frame_helplist = Frame(frame_help)
466 frame_helplist_buttons = Frame(frame_helplist)
467 scroll_helplist = Scrollbar(frame_helplist)
468 self.list_help = Listbox(
469 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000470 exportselection=FALSE)
terryjreedy938e7382017-06-26 20:48:39 -0400471 scroll_helplist.config(command=self.list_help.yview)
472 self.list_help.config(yscrollcommand=scroll_helplist.set)
473 self.list_help.bind('<ButtonRelease-1>', self.help_source_selected)
474 self.button_helplist_edit = Button(
475 frame_helplist_buttons, text='Edit', state=DISABLED,
476 width=8, command=self.helplist_item_edit)
477 self.button_helplist_add = Button(
478 frame_helplist_buttons, text='Add',
479 width=8, command=self.helplist_item_add)
480 self.button_helplist_remove = Button(
481 frame_helplist_buttons, text='Remove', state=DISABLED,
482 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400483
Steven M. Gava230e5782001-08-07 03:28:25 +0000484 #widget packing
485 #body
terryjreedy938e7382017-06-26 20:48:39 -0400486 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
487 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
488 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
489 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
490 #frame_run
491 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
492 self.radio_startup_shell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
493 self.radio_startup_edit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
494 #frame_save
495 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
496 self.radio_save_auto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
497 self.radio_save_ask.pack(side=RIGHT, anchor=W, padx=5, pady=5)
498 #frame_win_size
499 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
500 self.entry_win_height.pack(side=RIGHT, anchor=E, padx=10, pady=5)
501 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
502 self.entry_win_width.pack(side=RIGHT, anchor=E, padx=10, pady=5)
503 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
504 #frame_help
505 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
506 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
507 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
508 self.list_help.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
509 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
510 self.button_helplist_add.pack(side=TOP, anchor=W)
511 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000512 return frame
513
terryjreedy938e7382017-06-26 20:48:39 -0400514 def attach_var_callbacks(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400515 "Attach callbacks to variables that can be changed."
terryjreedy938e7382017-06-26 20:48:39 -0400516 self.font_size.trace_add('write', self.var_changed_font)
517 self.font_name.trace_add('write', self.var_changed_font)
518 self.font_bold.trace_add('write', self.var_changed_font)
519 self.space_num.trace_add('write', self.var_changed_space_num)
520 self.colour.trace_add('write', self.var_changed_colour)
521 self.builtin_theme.trace_add('write', self.var_changed_builtin_theme)
522 self.custom_theme.trace_add('write', self.var_changed_custom_theme)
523 self.is_builtin_theme.trace_add('write', self.var_changed_is_builtin_theme)
524 self.highlight_target.trace_add('write', self.var_changed_highlight_target)
525 self.keybinding.trace_add('write', self.var_changed_keybinding)
526 self.builtin_keys.trace_add('write', self.var_changed_builtin_keys)
527 self.custom_keys.trace_add('write', self.var_changed_custom_keys)
528 self.are_keys_builtin.trace_add('write', self.var_changed_are_keys_builtin)
529 self.win_width.trace_add('write', self.var_changed_win_width)
530 self.win_height.trace_add('write', self.var_changed_win_height)
531 self.startup_edit.trace_add('write', self.var_changed_startup_edit)
532 self.autosave.trace_add('write', self.var_changed_autosave)
Steven M. Gava052937f2002-02-11 02:20:53 +0000533
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400534 def remove_var_callbacks(self):
535 "Remove callbacks to prevent memory leaks."
536 for var in (
terryjreedy938e7382017-06-26 20:48:39 -0400537 self.font_size, self.font_name, self.font_bold,
538 self.space_num, self.colour, self.builtin_theme,
539 self.custom_theme, self.is_builtin_theme, self.highlight_target,
540 self.keybinding, self.builtin_keys, self.custom_keys,
541 self.are_keys_builtin, self.win_width, self.win_height,
terryjreedy8e3f73e2017-07-10 15:11:45 -0400542 self.startup_edit, self.autosave,):
Serhiy Storchaka81221742016-06-26 09:46:57 +0300543 var.trace_remove('write', var.trace_info()[0][1])
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400544
terryjreedy938e7382017-06-26 20:48:39 -0400545 def var_changed_font(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400546 """Store changes to font attributes.
547
548 When one font attribute changes, save them all, as they are
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400549 not independent from each other. In particular, when we are
550 overriding the default font, we need to write out everything.
terryjreedye5bb1122017-07-05 00:54:55 -0400551 """
terryjreedy938e7382017-06-26 20:48:39 -0400552 value = self.font_name.get()
terryjreedyedc03422017-07-07 16:37:39 -0400553 changes.add_option('main', 'EditorWindow', 'font', value)
terryjreedy938e7382017-06-26 20:48:39 -0400554 value = self.font_size.get()
terryjreedyedc03422017-07-07 16:37:39 -0400555 changes.add_option('main', 'EditorWindow', 'font-size', value)
terryjreedy938e7382017-06-26 20:48:39 -0400556 value = self.font_bold.get()
terryjreedyedc03422017-07-07 16:37:39 -0400557 changes.add_option('main', 'EditorWindow', 'font-bold', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000558
terryjreedy938e7382017-06-26 20:48:39 -0400559 def var_changed_space_num(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400560 "Store change to indentation size."
terryjreedy938e7382017-06-26 20:48:39 -0400561 value = self.space_num.get()
terryjreedyedc03422017-07-07 16:37:39 -0400562 changes.add_option('main', 'Indent', 'num-spaces', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000563
terryjreedy938e7382017-06-26 20:48:39 -0400564 def var_changed_colour(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400565 "Process change to color choice."
terryjreedy938e7382017-06-26 20:48:39 -0400566 self.on_new_colour_set()
Steven M. Gava052937f2002-02-11 02:20:53 +0000567
terryjreedy938e7382017-06-26 20:48:39 -0400568 def var_changed_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400569 """Process new builtin theme selection.
570
571 Add the changed theme's name to the changed_items and recreate
572 the sample with the values from the selected theme.
573 """
terryjreedy938e7382017-06-26 20:48:39 -0400574 old_themes = ('IDLE Classic', 'IDLE New')
575 value = self.builtin_theme.get()
576 if value not in old_themes:
577 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
terryjreedyedc03422017-07-07 16:37:39 -0400578 changes.add_option('main', 'Theme', 'name', old_themes[0])
579 changes.add_option('main', 'Theme', 'name2', value)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500580 self.new_custom_theme.config(text='New theme, see Help',
581 fg='#500000')
582 else:
terryjreedyedc03422017-07-07 16:37:39 -0400583 changes.add_option('main', 'Theme', 'name', value)
584 changes.add_option('main', 'Theme', 'name2', '')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500585 self.new_custom_theme.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400586 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000587
terryjreedy938e7382017-06-26 20:48:39 -0400588 def var_changed_custom_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400589 """Process new custom theme selection.
590
591 If a new custom theme is selected, add the name to the
592 changed_items and apply the theme to the sample.
593 """
terryjreedy938e7382017-06-26 20:48:39 -0400594 value = self.custom_theme.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000595 if value != '- no custom themes -':
terryjreedyedc03422017-07-07 16:37:39 -0400596 changes.add_option('main', 'Theme', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400597 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000598
terryjreedy938e7382017-06-26 20:48:39 -0400599 def var_changed_is_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400600 """Process toggle between builtin and custom theme.
601
602 Update the default toggle value and apply the newly
603 selected theme type.
604 """
terryjreedy938e7382017-06-26 20:48:39 -0400605 value = self.is_builtin_theme.get()
terryjreedyedc03422017-07-07 16:37:39 -0400606 changes.add_option('main', 'Theme', 'default', value)
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000607 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400608 self.var_changed_builtin_theme()
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000609 else:
terryjreedy938e7382017-06-26 20:48:39 -0400610 self.var_changed_custom_theme()
Steven M. Gava052937f2002-02-11 02:20:53 +0000611
terryjreedy938e7382017-06-26 20:48:39 -0400612 def var_changed_highlight_target(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400613 "Process selection of new target tag for highlighting."
terryjreedy938e7382017-06-26 20:48:39 -0400614 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000615
terryjreedy938e7382017-06-26 20:48:39 -0400616 def var_changed_keybinding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400617 "Store change to a keybinding."
terryjreedy938e7382017-06-26 20:48:39 -0400618 value = self.keybinding.get()
619 key_set = self.custom_keys.get()
620 event = self.list_bindings.get(ANCHOR).split()[0]
Steven M. Gavaa498af22002-02-01 01:33:36 +0000621 if idleConf.IsCoreBinding(event):
terryjreedyedc03422017-07-07 16:37:39 -0400622 changes.add_option('keys', key_set, event, value)
terryjreedye5bb1122017-07-05 00:54:55 -0400623 else: # Event is an extension binding.
terryjreedy938e7382017-06-26 20:48:39 -0400624 ext_name = idleConf.GetExtnNameForEvent(event)
625 ext_keybind_section = ext_name + '_cfgBindings'
terryjreedyedc03422017-07-07 16:37:39 -0400626 changes.add_option('extensions', ext_keybind_section, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000627
terryjreedy938e7382017-06-26 20:48:39 -0400628 def var_changed_builtin_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400629 "Process selection of builtin key set."
terryjreedy938e7382017-06-26 20:48:39 -0400630 old_keys = (
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400631 'IDLE Classic Windows',
632 'IDLE Classic Unix',
633 'IDLE Classic Mac',
634 'IDLE Classic OSX',
635 )
terryjreedy938e7382017-06-26 20:48:39 -0400636 value = self.builtin_keys.get()
637 if value not in old_keys:
638 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
terryjreedyedc03422017-07-07 16:37:39 -0400639 changes.add_option('main', 'Keys', 'name', old_keys[0])
640 changes.add_option('main', 'Keys', 'name2', value)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400641 self.new_custom_keys.config(text='New key set, see Help',
642 fg='#500000')
643 else:
terryjreedyedc03422017-07-07 16:37:39 -0400644 changes.add_option('main', 'Keys', 'name', value)
645 changes.add_option('main', 'Keys', 'name2', '')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400646 self.new_custom_keys.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400647 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000648
terryjreedy938e7382017-06-26 20:48:39 -0400649 def var_changed_custom_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400650 "Process selection of custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400651 value = self.custom_keys.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000652 if value != '- no custom keys -':
terryjreedyedc03422017-07-07 16:37:39 -0400653 changes.add_option('main', 'Keys', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400654 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000655
terryjreedy938e7382017-06-26 20:48:39 -0400656 def var_changed_are_keys_builtin(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400657 "Process toggle between builtin key set and custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400658 value = self.are_keys_builtin.get()
terryjreedyedc03422017-07-07 16:37:39 -0400659 changes.add_option('main', 'Keys', 'default', value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000660 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400661 self.var_changed_builtin_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000662 else:
terryjreedy938e7382017-06-26 20:48:39 -0400663 self.var_changed_custom_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000664
terryjreedy938e7382017-06-26 20:48:39 -0400665 def var_changed_win_width(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400666 "Store change to window width."
terryjreedy938e7382017-06-26 20:48:39 -0400667 value = self.win_width.get()
terryjreedyedc03422017-07-07 16:37:39 -0400668 changes.add_option('main', 'EditorWindow', 'width', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000669
terryjreedy938e7382017-06-26 20:48:39 -0400670 def var_changed_win_height(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400671 "Store change to window height."
terryjreedy938e7382017-06-26 20:48:39 -0400672 value = self.win_height.get()
terryjreedyedc03422017-07-07 16:37:39 -0400673 changes.add_option('main', 'EditorWindow', 'height', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000674
terryjreedy938e7382017-06-26 20:48:39 -0400675 def var_changed_startup_edit(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400676 "Store change to toggle for starting IDLE in the editor or shell."
terryjreedy938e7382017-06-26 20:48:39 -0400677 value = self.startup_edit.get()
terryjreedyedc03422017-07-07 16:37:39 -0400678 changes.add_option('main', 'General', 'editor-on-startup', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000679
terryjreedy938e7382017-06-26 20:48:39 -0400680 def var_changed_autosave(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400681 "Store change to autosave."
terryjreedy938e7382017-06-26 20:48:39 -0400682 value = self.autosave.get()
terryjreedyedc03422017-07-07 16:37:39 -0400683 changes.add_option('main', 'General', 'autosave', value)
Kurt B. Kaiser6c638b62003-05-26 06:23:10 +0000684
terryjreedy938e7382017-06-26 20:48:39 -0400685 def set_theme_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400686 "Set available screen options based on builtin or custom theme."
terryjreedy938e7382017-06-26 20:48:39 -0400687 if self.is_builtin_theme.get():
688 self.opt_menu_theme_builtin.config(state=NORMAL)
689 self.opt_menu_theme_custom.config(state=DISABLED)
690 self.button_delete_custom_theme.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000691 else:
terryjreedy938e7382017-06-26 20:48:39 -0400692 self.opt_menu_theme_builtin.config(state=DISABLED)
693 self.radio_theme_custom.config(state=NORMAL)
694 self.opt_menu_theme_custom.config(state=NORMAL)
695 self.button_delete_custom_theme.config(state=NORMAL)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000696
terryjreedy938e7382017-06-26 20:48:39 -0400697 def set_keys_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400698 "Set available screen options based on builtin or custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400699 if self.are_keys_builtin.get():
700 self.opt_menu_keys_builtin.config(state=NORMAL)
701 self.opt_menu_keys_custom.config(state=DISABLED)
702 self.button_delete_custom_keys.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000703 else:
terryjreedy938e7382017-06-26 20:48:39 -0400704 self.opt_menu_keys_builtin.config(state=DISABLED)
705 self.radio_keys_custom.config(state=NORMAL)
706 self.opt_menu_keys_custom.config(state=NORMAL)
707 self.button_delete_custom_keys.config(state=NORMAL)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000708
terryjreedy938e7382017-06-26 20:48:39 -0400709 def get_new_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400710 """Handle event to change key binding for selected line.
711
712 A selection of a key/binding in the list of current
713 bindings pops up a dialog to enter a new binding. If
714 the current key set is builtin and a binding has
715 changed, then a name for a custom key set needs to be
716 entered for the change to be applied.
717 """
terryjreedy938e7382017-06-26 20:48:39 -0400718 list_index = self.list_bindings.index(ANCHOR)
719 binding = self.list_bindings.get(list_index)
terryjreedye5bb1122017-07-05 00:54:55 -0400720 bind_name = binding.split()[0]
terryjreedy938e7382017-06-26 20:48:39 -0400721 if self.are_keys_builtin.get():
722 current_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000723 else:
terryjreedy938e7382017-06-26 20:48:39 -0400724 current_key_set_name = self.custom_keys.get()
725 current_bindings = idleConf.GetCurrentKeySet()
terryjreedyedc03422017-07-07 16:37:39 -0400726 if current_key_set_name in changes['keys']: # unsaved changes
727 key_set_changes = changes['keys'][current_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400728 for event in key_set_changes:
729 current_bindings[event] = key_set_changes[event].split()
730 current_key_sequences = list(current_bindings.values())
731 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
732 current_key_sequences).result
terryjreedye5bb1122017-07-05 00:54:55 -0400733 if new_keys:
734 if self.are_keys_builtin.get(): # Current key set is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400735 message = ('Your changes will be saved as a new Custom Key Set.'
736 ' Enter a name for your new Custom Key Set below.')
terryjreedy938e7382017-06-26 20:48:39 -0400737 new_keyset = self.get_new_keys_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400738 if not new_keyset: # User cancelled custom key set creation.
terryjreedy938e7382017-06-26 20:48:39 -0400739 self.list_bindings.select_set(list_index)
740 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000741 return
terryjreedye5bb1122017-07-05 00:54:55 -0400742 else: # Create new custom key set based on previously active key set.
terryjreedy938e7382017-06-26 20:48:39 -0400743 self.create_new_key_set(new_keyset)
744 self.list_bindings.delete(list_index)
745 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
746 self.list_bindings.select_set(list_index)
747 self.list_bindings.select_anchor(list_index)
748 self.keybinding.set(new_keys)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000749 else:
terryjreedy938e7382017-06-26 20:48:39 -0400750 self.list_bindings.select_set(list_index)
751 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000752
terryjreedy938e7382017-06-26 20:48:39 -0400753 def get_new_keys_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400754 "Return new key set name from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400755 used_names = (idleConf.GetSectionList('user', 'keys') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400756 idleConf.GetSectionList('default', 'keys'))
terryjreedy938e7382017-06-26 20:48:39 -0400757 new_keyset = SectionName(
758 self, 'New Custom Key Set', message, used_names).result
759 return new_keyset
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000760
terryjreedy938e7382017-06-26 20:48:39 -0400761 def save_as_new_key_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400762 "Prompt for name of new key set and save changes using that name."
terryjreedy938e7382017-06-26 20:48:39 -0400763 new_keys_name = self.get_new_keys_name('New Key Set Name:')
764 if new_keys_name:
765 self.create_new_key_set(new_keys_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000766
terryjreedy938e7382017-06-26 20:48:39 -0400767 def keybinding_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -0400768 "Activate button to assign new keys to selected action."
terryjreedy938e7382017-06-26 20:48:39 -0400769 self.button_new_keys.config(state=NORMAL)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000770
terryjreedy938e7382017-06-26 20:48:39 -0400771 def create_new_key_set(self, new_key_set_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400772 """Create a new custom key set with the given name.
773
774 Create the new key set based on the previously active set
775 with the current changes applied. Once it is saved, then
776 activate the new key set.
777 """
terryjreedy938e7382017-06-26 20:48:39 -0400778 if self.are_keys_builtin.get():
779 prev_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000780 else:
terryjreedy938e7382017-06-26 20:48:39 -0400781 prev_key_set_name = self.custom_keys.get()
782 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
783 new_keys = {}
terryjreedye5bb1122017-07-05 00:54:55 -0400784 for event in prev_keys: # Add key set to changed items.
785 event_name = event[2:-2] # Trim off the angle brackets.
terryjreedy938e7382017-06-26 20:48:39 -0400786 binding = ' '.join(prev_keys[event])
787 new_keys[event_name] = binding
terryjreedye5bb1122017-07-05 00:54:55 -0400788 # Handle any unsaved changes to prev key set.
terryjreedyedc03422017-07-07 16:37:39 -0400789 if prev_key_set_name in changes['keys']:
790 key_set_changes = changes['keys'][prev_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400791 for event in key_set_changes:
792 new_keys[event] = key_set_changes[event]
terryjreedye5bb1122017-07-05 00:54:55 -0400793 # Save the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400794 self.save_new_key_set(new_key_set_name, new_keys)
terryjreedye5bb1122017-07-05 00:54:55 -0400795 # Change GUI over to the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400796 custom_key_list = idleConf.GetSectionList('user', 'keys')
797 custom_key_list.sort()
798 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
799 self.are_keys_builtin.set(0)
800 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000801
terryjreedy938e7382017-06-26 20:48:39 -0400802 def load_keys_list(self, keyset_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400803 """Reload the list of action/key binding pairs for the active key set.
804
805 An action/key binding can be selected to change the key binding.
806 """
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400807 reselect = 0
terryjreedy938e7382017-06-26 20:48:39 -0400808 if self.list_bindings.curselection():
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400809 reselect = 1
terryjreedy938e7382017-06-26 20:48:39 -0400810 list_index = self.list_bindings.index(ANCHOR)
811 keyset = idleConf.GetKeySet(keyset_name)
812 bind_names = list(keyset.keys())
813 bind_names.sort()
814 self.list_bindings.delete(0, END)
815 for bind_name in bind_names:
terryjreedye5bb1122017-07-05 00:54:55 -0400816 key = ' '.join(keyset[bind_name])
817 bind_name = bind_name[2:-2] # Trim off the angle brackets.
terryjreedyedc03422017-07-07 16:37:39 -0400818 if keyset_name in changes['keys']:
terryjreedye5bb1122017-07-05 00:54:55 -0400819 # Handle any unsaved changes to this key set.
terryjreedyedc03422017-07-07 16:37:39 -0400820 if bind_name in changes['keys'][keyset_name]:
821 key = changes['keys'][keyset_name][bind_name]
terryjreedy938e7382017-06-26 20:48:39 -0400822 self.list_bindings.insert(END, bind_name+' - '+key)
Steven M. Gava052937f2002-02-11 02:20:53 +0000823 if reselect:
terryjreedy938e7382017-06-26 20:48:39 -0400824 self.list_bindings.see(list_index)
825 self.list_bindings.select_set(list_index)
826 self.list_bindings.select_anchor(list_index)
Steven M. Gava052937f2002-02-11 02:20:53 +0000827
terryjreedy938e7382017-06-26 20:48:39 -0400828 def delete_custom_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400829 """Handle event to delete a custom key set.
830
831 Applying the delete deactivates the current configuration and
832 reverts to the default. The custom key set is permanently
833 deleted from the config file.
834 """
terryjreedy938e7382017-06-26 20:48:39 -0400835 keyset_name=self.custom_keys.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400836 delmsg = 'Are you sure you wish to delete the key set %r ?'
837 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400838 'Delete Key Set', delmsg % keyset_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000839 return
terryjreedy938e7382017-06-26 20:48:39 -0400840 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400841 # Remove key set from changes, config, and file.
terryjreedyc0179482017-07-11 19:50:10 -0400842 changes.delete_section('keys', keyset_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400843 # Reload user key set list.
terryjreedy938e7382017-06-26 20:48:39 -0400844 item_list = idleConf.GetSectionList('user', 'keys')
845 item_list.sort()
846 if not item_list:
847 self.radio_keys_custom.config(state=DISABLED)
848 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
Steven M. Gava49745752002-02-18 01:43:11 +0000849 else:
terryjreedy938e7382017-06-26 20:48:39 -0400850 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400851 # Revert to default key set.
terryjreedy938e7382017-06-26 20:48:39 -0400852 self.are_keys_builtin.set(idleConf.defaultCfg['main']
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400853 .Get('Keys', 'default'))
terryjreedy938e7382017-06-26 20:48:39 -0400854 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400855 or idleConf.default_keys())
terryjreedye5bb1122017-07-05 00:54:55 -0400856 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400857 changes.save_all()
858 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400859 self.activate_config_changes()
860 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000861
terryjreedy938e7382017-06-26 20:48:39 -0400862 def delete_custom_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400863 """Handle event to delete custom theme.
864
865 The current theme is deactivated and the default theme is
866 activated. The custom theme is permanently removed from
867 the config file.
868 """
terryjreedy938e7382017-06-26 20:48:39 -0400869 theme_name = self.custom_theme.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400870 delmsg = 'Are you sure you wish to delete the theme %r ?'
871 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400872 'Delete Theme', delmsg % theme_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000873 return
terryjreedy938e7382017-06-26 20:48:39 -0400874 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400875 # Remove theme from changes, config, and file.
terryjreedyc0179482017-07-11 19:50:10 -0400876 changes.delete_section('highlight', theme_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400877 # Reload user theme list.
terryjreedy938e7382017-06-26 20:48:39 -0400878 item_list = idleConf.GetSectionList('user', 'highlight')
879 item_list.sort()
880 if not item_list:
881 self.radio_theme_custom.config(state=DISABLED)
882 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
Steven M. Gava49745752002-02-18 01:43:11 +0000883 else:
terryjreedy938e7382017-06-26 20:48:39 -0400884 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400885 # Revert to default theme.
terryjreedy938e7382017-06-26 20:48:39 -0400886 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
887 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
terryjreedye5bb1122017-07-05 00:54:55 -0400888 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400889 changes.save_all()
890 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400891 self.activate_config_changes()
892 self.set_theme_type()
Steven M. Gava49745752002-02-18 01:43:11 +0000893
terryjreedy938e7382017-06-26 20:48:39 -0400894 def get_colour(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400895 """Handle button to select a new color for the target tag.
896
897 If a new color is selected while using a builtin theme, a
898 name must be supplied to create a custom theme.
899 """
terryjreedy938e7382017-06-26 20:48:39 -0400900 target = self.highlight_target.get()
901 prev_colour = self.frame_colour_set.cget('bg')
902 rgbTuplet, colour_string = tkColorChooser.askcolor(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400903 parent=self, title='Pick new colour for : '+target,
terryjreedy938e7382017-06-26 20:48:39 -0400904 initialcolor=prev_colour)
905 if colour_string and (colour_string != prev_colour):
terryjreedye5bb1122017-07-05 00:54:55 -0400906 # User didn't cancel and they chose a new colour.
907 if self.is_builtin_theme.get(): # Current theme is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400908 message = ('Your changes will be saved as a new Custom Theme. '
909 'Enter a name for your new Custom Theme below.')
terryjreedy938e7382017-06-26 20:48:39 -0400910 new_theme = self.get_new_theme_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400911 if not new_theme: # User cancelled custom theme creation.
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000912 return
terryjreedye5bb1122017-07-05 00:54:55 -0400913 else: # Create new custom theme based on previously active theme.
terryjreedy938e7382017-06-26 20:48:39 -0400914 self.create_new_theme(new_theme)
915 self.colour.set(colour_string)
terryjreedye5bb1122017-07-05 00:54:55 -0400916 else: # Current theme is user defined.
terryjreedy938e7382017-06-26 20:48:39 -0400917 self.colour.set(colour_string)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000918
terryjreedy938e7382017-06-26 20:48:39 -0400919 def on_new_colour_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400920 "Display sample of new color selection on the dialog."
terryjreedy938e7382017-06-26 20:48:39 -0400921 new_colour=self.colour.get()
terryjreedye5bb1122017-07-05 00:54:55 -0400922 self.frame_colour_set.config(bg=new_colour) # Set sample.
terryjreedy938e7382017-06-26 20:48:39 -0400923 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
924 sample_element = self.theme_elements[self.highlight_target.get()][0]
925 self.text_highlight_sample.tag_config(sample_element, **{plane:new_colour})
926 theme = self.custom_theme.get()
927 theme_element = sample_element + '-' + plane
terryjreedyedc03422017-07-07 16:37:39 -0400928 changes.add_option('highlight', theme, theme_element, new_colour)
Steven M. Gava052937f2002-02-11 02:20:53 +0000929
terryjreedy938e7382017-06-26 20:48:39 -0400930 def get_new_theme_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400931 "Return name of new theme from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400932 used_names = (idleConf.GetSectionList('user', 'highlight') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400933 idleConf.GetSectionList('default', 'highlight'))
terryjreedy938e7382017-06-26 20:48:39 -0400934 new_theme = SectionName(
935 self, 'New Custom Theme', message, used_names).result
936 return new_theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000937
terryjreedy938e7382017-06-26 20:48:39 -0400938 def save_as_new_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400939 "Prompt for new theme name and create the theme."
terryjreedy938e7382017-06-26 20:48:39 -0400940 new_theme_name = self.get_new_theme_name('New Theme Name:')
941 if new_theme_name:
942 self.create_new_theme(new_theme_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000943
terryjreedy938e7382017-06-26 20:48:39 -0400944 def create_new_theme(self, new_theme_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400945 """Create a new custom theme with the given name.
946
947 Create the new theme based on the previously active theme
948 with the current changes applied. Once it is saved, then
949 activate the new theme.
950 """
terryjreedy938e7382017-06-26 20:48:39 -0400951 if self.is_builtin_theme.get():
952 theme_type = 'default'
953 theme_name = self.builtin_theme.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000954 else:
terryjreedy938e7382017-06-26 20:48:39 -0400955 theme_type = 'user'
956 theme_name = self.custom_theme.get()
957 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400958 # Apply any of the old theme's unsaved changes to the new theme.
terryjreedyedc03422017-07-07 16:37:39 -0400959 if theme_name in changes['highlight']:
960 theme_changes = changes['highlight'][theme_name]
terryjreedy938e7382017-06-26 20:48:39 -0400961 for element in theme_changes:
962 new_theme[element] = theme_changes[element]
terryjreedye5bb1122017-07-05 00:54:55 -0400963 # Save the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400964 self.save_new_theme(new_theme_name, new_theme)
terryjreedye5bb1122017-07-05 00:54:55 -0400965 # Change GUI over to the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400966 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
967 custom_theme_list.sort()
968 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
969 self.is_builtin_theme.set(0)
970 self.set_theme_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000971
terryjreedy7ab33422017-07-09 19:26:32 -0400972 def on_fontlist_select(self, event):
973 """Handle selecting a font from the list.
terryjreedye5bb1122017-07-05 00:54:55 -0400974
terryjreedy7ab33422017-07-09 19:26:32 -0400975 Event can result from either mouse click or Up or Down key.
976 Set font_name and example display to selection.
terryjreedye5bb1122017-07-05 00:54:55 -0400977 """
terryjreedy953e5272017-07-11 02:16:41 -0400978 font = self.fontlist.get(
979 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
terryjreedy938e7382017-06-26 20:48:39 -0400980 self.font_name.set(font.lower())
981 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000982
terryjreedy938e7382017-06-26 20:48:39 -0400983 def set_font_sample(self, event=None):
terryjreedye5bb1122017-07-05 00:54:55 -0400984 "Update the screen samples with the font settings from the dialog."
terryjreedy938e7382017-06-26 20:48:39 -0400985 font_name = self.font_name.get()
986 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
987 new_font = (font_name, self.font_size.get(), font_weight)
988 self.font_sample.config(font=new_font)
989 self.text_highlight_sample.configure(font=new_font)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000990
terryjreedy938e7382017-06-26 20:48:39 -0400991 def set_highlight_target(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400992 "Set fg/bg toggle and color based on highlight tag target."
993 if self.highlight_target.get() == 'Cursor': # bg not possible
terryjreedy938e7382017-06-26 20:48:39 -0400994 self.radio_fg.config(state=DISABLED)
995 self.radio_bg.config(state=DISABLED)
996 self.fg_bg_toggle.set(1)
terryjreedye5bb1122017-07-05 00:54:55 -0400997 else: # Both fg and bg can be set.
terryjreedy938e7382017-06-26 20:48:39 -0400998 self.radio_fg.config(state=NORMAL)
999 self.radio_bg.config(state=NORMAL)
1000 self.fg_bg_toggle.set(1)
1001 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001002
terryjreedy938e7382017-06-26 20:48:39 -04001003 def set_colour_sample_binding(self, *args):
terryjreedye5bb1122017-07-05 00:54:55 -04001004 "Change color sample based on foreground/background toggle."
terryjreedy938e7382017-06-26 20:48:39 -04001005 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001006
terryjreedy938e7382017-06-26 20:48:39 -04001007 def set_colour_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001008 "Set the color of the frame background to reflect the selected target."
1009 # Set the colour sample area.
terryjreedy938e7382017-06-26 20:48:39 -04001010 tag = self.theme_elements[self.highlight_target.get()][0]
1011 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1012 colour = self.text_highlight_sample.tag_cget(tag, plane)
1013 self.frame_colour_set.config(bg=colour)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001014
terryjreedy938e7382017-06-26 20:48:39 -04001015 def paint_theme_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001016 "Apply the theme colors to each element tag in the sample text."
1017 if self.is_builtin_theme.get(): # Default theme
terryjreedy938e7382017-06-26 20:48:39 -04001018 theme = self.builtin_theme.get()
terryjreedye5bb1122017-07-05 00:54:55 -04001019 else: # User theme
terryjreedy938e7382017-06-26 20:48:39 -04001020 theme = self.custom_theme.get()
1021 for element_title in self.theme_elements:
1022 element = self.theme_elements[element_title][0]
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001023 colours = idleConf.GetHighlight(theme, element)
terryjreedye5bb1122017-07-05 00:54:55 -04001024 if element == 'cursor': # Cursor sample needs special painting.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001025 colours['background'] = idleConf.GetHighlight(
1026 theme, 'normal', fgBg='bg')
terryjreedye5bb1122017-07-05 00:54:55 -04001027 # Handle any unsaved changes to this theme.
terryjreedyedc03422017-07-07 16:37:39 -04001028 if theme in changes['highlight']:
1029 theme_dict = changes['highlight'][theme]
terryjreedy938e7382017-06-26 20:48:39 -04001030 if element + '-foreground' in theme_dict:
1031 colours['foreground'] = theme_dict[element + '-foreground']
1032 if element + '-background' in theme_dict:
1033 colours['background'] = theme_dict[element + '-background']
1034 self.text_highlight_sample.tag_config(element, **colours)
1035 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001036
terryjreedy938e7382017-06-26 20:48:39 -04001037 def help_source_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001038 "Handle event for selecting additional help."
terryjreedy938e7382017-06-26 20:48:39 -04001039 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001040
terryjreedy938e7382017-06-26 20:48:39 -04001041 def set_helplist_button_states(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001042 "Toggle the state for the help list buttons based on list entries."
1043 if self.list_help.size() < 1: # No entries in list.
terryjreedy938e7382017-06-26 20:48:39 -04001044 self.button_helplist_edit.config(state=DISABLED)
1045 self.button_helplist_remove.config(state=DISABLED)
terryjreedye5bb1122017-07-05 00:54:55 -04001046 else: # Some entries.
1047 if self.list_help.curselection(): # There currently is a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001048 self.button_helplist_edit.config(state=NORMAL)
1049 self.button_helplist_remove.config(state=NORMAL)
terryjreedye5bb1122017-07-05 00:54:55 -04001050 else: # There currently is not a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001051 self.button_helplist_edit.config(state=DISABLED)
1052 self.button_helplist_remove.config(state=DISABLED)
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001053
terryjreedy938e7382017-06-26 20:48:39 -04001054 def helplist_item_add(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001055 """Handle add button for the help list.
1056
1057 Query for name and location of new help sources and add
1058 them to the list.
1059 """
terryjreedy938e7382017-06-26 20:48:39 -04001060 help_source = HelpSource(self, 'New Help Source',
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001061 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001062 if help_source:
1063 self.user_helplist.append((help_source[0], help_source[1]))
1064 self.list_help.insert(END, help_source[0])
1065 self.update_user_help_changed_items()
1066 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001067
terryjreedy938e7382017-06-26 20:48:39 -04001068 def helplist_item_edit(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001069 """Handle edit button for the help list.
1070
1071 Query with existing help source information and update
1072 config if the values are changed.
1073 """
terryjreedy938e7382017-06-26 20:48:39 -04001074 item_index = self.list_help.index(ANCHOR)
1075 help_source = self.user_helplist[item_index]
1076 new_help_source = HelpSource(
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001077 self, 'Edit Help Source',
terryjreedy938e7382017-06-26 20:48:39 -04001078 menuitem=help_source[0],
1079 filepath=help_source[1],
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001080 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001081 if new_help_source and new_help_source != help_source:
1082 self.user_helplist[item_index] = new_help_source
1083 self.list_help.delete(item_index)
1084 self.list_help.insert(item_index, new_help_source[0])
1085 self.update_user_help_changed_items()
1086 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001087
terryjreedy938e7382017-06-26 20:48:39 -04001088 def helplist_item_remove(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001089 """Handle remove button for the help list.
1090
1091 Delete the help list item from config.
1092 """
terryjreedy938e7382017-06-26 20:48:39 -04001093 item_index = self.list_help.index(ANCHOR)
1094 del(self.user_helplist[item_index])
1095 self.list_help.delete(item_index)
1096 self.update_user_help_changed_items()
1097 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001098
terryjreedy938e7382017-06-26 20:48:39 -04001099 def update_user_help_changed_items(self):
terryjreedyedc03422017-07-07 16:37:39 -04001100 "Clear and rebuild the HelpFiles section in changes"
1101 changes['main']['HelpFiles'] = {}
terryjreedy938e7382017-06-26 20:48:39 -04001102 for num in range(1, len(self.user_helplist) + 1):
terryjreedyedc03422017-07-07 16:37:39 -04001103 changes.add_option(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001104 'main', 'HelpFiles', str(num),
terryjreedy938e7382017-06-26 20:48:39 -04001105 ';'.join(self.user_helplist[num-1][:2]))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001106
terryjreedy938e7382017-06-26 20:48:39 -04001107 def load_font_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001108 "Load current configuration settings for the font options."
1109 # Set base editor font selection list.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001110 fonts = list(tkFont.families(self))
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001111 fonts.sort()
1112 for font in fonts:
terryjreedy7ab33422017-07-09 19:26:32 -04001113 self.fontlist.insert(END, font)
terryjreedy938e7382017-06-26 20:48:39 -04001114 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
1115 font_name = configured_font[0].lower()
1116 font_size = configured_font[1]
1117 font_bold = configured_font[2]=='bold'
1118 self.font_name.set(font_name)
Kurt B. Kaiser05391692003-05-26 20:35:53 +00001119 lc_fonts = [s.lower() for s in fonts]
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001120 try:
terryjreedy938e7382017-06-26 20:48:39 -04001121 current_font_index = lc_fonts.index(font_name)
terryjreedy7ab33422017-07-09 19:26:32 -04001122 self.fontlist.see(current_font_index)
1123 self.fontlist.select_set(current_font_index)
1124 self.fontlist.select_anchor(current_font_index)
1125 self.fontlist.activate(current_font_index)
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001126 except ValueError:
1127 pass
terryjreedye5bb1122017-07-05 00:54:55 -04001128 # Set font size dropdown.
terryjreedy938e7382017-06-26 20:48:39 -04001129 self.opt_menu_font_size.SetMenu(('7', '8', '9', '10', '11', '12', '13',
Terry Jan Reedyda028872016-08-30 20:19:13 -04001130 '14', '16', '18', '20', '22',
terryjreedy938e7382017-06-26 20:48:39 -04001131 '25', '29', '34', '40'), font_size )
terryjreedye5bb1122017-07-05 00:54:55 -04001132 # Set font weight.
terryjreedy938e7382017-06-26 20:48:39 -04001133 self.font_bold.set(font_bold)
terryjreedye5bb1122017-07-05 00:54:55 -04001134 # Set font sample.
terryjreedy938e7382017-06-26 20:48:39 -04001135 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001136
terryjreedy938e7382017-06-26 20:48:39 -04001137 def load_tab_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001138 "Load current configuration settings for the tab options."
1139 # Set indent sizes.
terryjreedy938e7382017-06-26 20:48:39 -04001140 space_num = idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001141 'main', 'Indent', 'num-spaces', default=4, type='int')
terryjreedy938e7382017-06-26 20:48:39 -04001142 self.space_num.set(space_num)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001143
terryjreedy938e7382017-06-26 20:48:39 -04001144 def load_theme_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001145 "Load current configuration settings for the theme options."
1146 # Set current theme type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001147 self.is_builtin_theme.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001148 'main', 'Theme', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001149 # Set current theme.
terryjreedy938e7382017-06-26 20:48:39 -04001150 current_option = idleConf.CurrentTheme()
terryjreedye5bb1122017-07-05 00:54:55 -04001151 # Load available theme option menus.
1152 if self.is_builtin_theme.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001153 item_list = idleConf.GetSectionList('default', 'highlight')
1154 item_list.sort()
1155 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
1156 item_list = idleConf.GetSectionList('user', 'highlight')
1157 item_list.sort()
1158 if not item_list:
1159 self.radio_theme_custom.config(state=DISABLED)
1160 self.custom_theme.set('- no custom themes -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001161 else:
terryjreedy938e7382017-06-26 20:48:39 -04001162 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001163 else: # User theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001164 item_list = idleConf.GetSectionList('user', 'highlight')
1165 item_list.sort()
1166 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
1167 item_list = idleConf.GetSectionList('default', 'highlight')
1168 item_list.sort()
1169 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
1170 self.set_theme_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001171 # Load theme element option menu.
terryjreedy938e7382017-06-26 20:48:39 -04001172 theme_names = list(self.theme_elements.keys())
1173 theme_names.sort(key=lambda x: self.theme_elements[x][1])
1174 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
1175 self.paint_theme_sample()
1176 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001177
terryjreedy938e7382017-06-26 20:48:39 -04001178 def load_key_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001179 "Load current configuration settings for the keybinding options."
1180 # Set current keys type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001181 self.are_keys_builtin.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001182 'main', 'Keys', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001183 # Set current keys.
terryjreedy938e7382017-06-26 20:48:39 -04001184 current_option = idleConf.CurrentKeys()
terryjreedye5bb1122017-07-05 00:54:55 -04001185 # Load available keyset option menus.
1186 if self.are_keys_builtin.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001187 item_list = idleConf.GetSectionList('default', 'keys')
1188 item_list.sort()
1189 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1190 item_list = idleConf.GetSectionList('user', 'keys')
1191 item_list.sort()
1192 if not item_list:
1193 self.radio_keys_custom.config(state=DISABLED)
1194 self.custom_keys.set('- no custom keys -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001195 else:
terryjreedy938e7382017-06-26 20:48:39 -04001196 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001197 else: # User key set selected.
terryjreedy938e7382017-06-26 20:48:39 -04001198 item_list = idleConf.GetSectionList('user', 'keys')
1199 item_list.sort()
1200 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1201 item_list = idleConf.GetSectionList('default', 'keys')
1202 item_list.sort()
1203 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1204 self.set_keys_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001205 # Load keyset element list.
terryjreedy938e7382017-06-26 20:48:39 -04001206 keyset_name = idleConf.CurrentKeys()
1207 self.load_keys_list(keyset_name)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001208
terryjreedy938e7382017-06-26 20:48:39 -04001209 def load_general_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001210 "Load current configuration settings for the general options."
1211 # Set startup state.
terryjreedy938e7382017-06-26 20:48:39 -04001212 self.startup_edit.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001213 'main', 'General', 'editor-on-startup', default=1, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001214 # Set autosave state.
terryjreedy938e7382017-06-26 20:48:39 -04001215 self.autosave.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001216 'main', 'General', 'autosave', default=0, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001217 # Set initial window size.
terryjreedy938e7382017-06-26 20:48:39 -04001218 self.win_width.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001219 'main', 'EditorWindow', 'width', type='int'))
terryjreedy938e7382017-06-26 20:48:39 -04001220 self.win_height.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001221 'main', 'EditorWindow', 'height', type='int'))
terryjreedye5bb1122017-07-05 00:54:55 -04001222 # Set additional help sources.
terryjreedy938e7382017-06-26 20:48:39 -04001223 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1224 for help_item in self.user_helplist:
1225 self.list_help.insert(END, help_item[0])
1226 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001227
terryjreedy938e7382017-06-26 20:48:39 -04001228 def load_configs(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001229 """Load configuration for each page.
1230
1231 Load configuration from default and user config files and populate
Steven M. Gava429a86af2001-10-23 10:42:12 +00001232 the widgets on the config dialog pages.
1233 """
terryjreedy938e7382017-06-26 20:48:39 -04001234 self.load_font_cfg()
1235 self.load_tab_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001236 self.load_theme_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001237 self.load_key_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001238 self.load_general_cfg()
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001239 # note: extension page handled separately
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001240
terryjreedy938e7382017-06-26 20:48:39 -04001241 def save_new_key_set(self, keyset_name, keyset):
terryjreedye5bb1122017-07-05 00:54:55 -04001242 """Save a newly created core key set.
1243
terryjreedy938e7382017-06-26 20:48:39 -04001244 keyset_name - string, the name of the new key set
1245 keyset - dictionary containing the new key set
Steven M. Gava052937f2002-02-11 02:20:53 +00001246 """
terryjreedy938e7382017-06-26 20:48:39 -04001247 if not idleConf.userCfg['keys'].has_section(keyset_name):
1248 idleConf.userCfg['keys'].add_section(keyset_name)
1249 for event in keyset:
1250 value = keyset[event]
1251 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001252
terryjreedy938e7382017-06-26 20:48:39 -04001253 def save_new_theme(self, theme_name, theme):
terryjreedye5bb1122017-07-05 00:54:55 -04001254 """Save a newly created theme.
1255
terryjreedy938e7382017-06-26 20:48:39 -04001256 theme_name - string, the name of the new theme
Steven M. Gava052937f2002-02-11 02:20:53 +00001257 theme - dictionary containing the new theme
1258 """
terryjreedy938e7382017-06-26 20:48:39 -04001259 if not idleConf.userCfg['highlight'].has_section(theme_name):
1260 idleConf.userCfg['highlight'].add_section(theme_name)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001261 for element in theme:
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001262 value = theme[element]
terryjreedy938e7382017-06-26 20:48:39 -04001263 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001264
terryjreedy938e7382017-06-26 20:48:39 -04001265 def deactivate_current_config(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001266 "Remove current key bindings."
1267 # Before a config is saved, some cleanup of current
1268 # config must be done - remove the previous keybindings.
terryjreedy938e7382017-06-26 20:48:39 -04001269 win_instances = self.parent.instance_dict.keys()
1270 for instance in win_instances:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001271 instance.RemoveKeybindings()
1272
terryjreedy938e7382017-06-26 20:48:39 -04001273 def activate_config_changes(self):
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001274 "Dynamically apply configuration changes"
terryjreedy938e7382017-06-26 20:48:39 -04001275 win_instances = self.parent.instance_dict.keys()
1276 for instance in win_instances:
Steven M. Gavab77d3432002-03-02 07:16:21 +00001277 instance.ResetColorizer()
Steven M. Gavab1585412002-03-12 00:21:56 +00001278 instance.ResetFont()
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001279 instance.set_notabs_indentwidth()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001280 instance.ApplyKeybindings()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +00001281 instance.reset_help_menu_entries()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001282
terryjreedy938e7382017-06-26 20:48:39 -04001283 def cancel(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001284 "Dismiss config dialog."
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001285 self.destroy()
1286
terryjreedy938e7382017-06-26 20:48:39 -04001287 def ok(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001288 "Apply config changes, then dismiss dialog."
terryjreedy938e7382017-06-26 20:48:39 -04001289 self.apply()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001290 self.destroy()
1291
terryjreedy938e7382017-06-26 20:48:39 -04001292 def apply(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001293 "Apply config changes and leave dialog open."
terryjreedy938e7382017-06-26 20:48:39 -04001294 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -04001295 changes.save_all()
1296 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -04001297 self.activate_config_changes()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001298
terryjreedy938e7382017-06-26 20:48:39 -04001299 def help(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001300 "Create textview for config dialog help."
terryjreedy938e7382017-06-26 20:48:39 -04001301 page = self.tab_pages._current_page
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001302 view_text(self, title='Help for IDLE preferences',
1303 text=help_common+help_pages.get(page, ''))
1304
terryjreedy938e7382017-06-26 20:48:39 -04001305 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001306 """Part of the config dialog used for configuring IDLE extensions.
1307
1308 This code is generic - it works for any and all IDLE extensions.
1309
1310 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001311 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001312 GUI interface to change the configuration values, and saves the
1313 changes using idleConf.
1314
1315 Not all changes take effect immediately - some may require restarting IDLE.
1316 This depends on each extension's implementation.
1317
1318 All values are treated as text, and it is up to the user to supply
1319 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001320 which are boolean, and can be toggled with a True/False button.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001321 """
1322 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -04001323 frame = self.tab_pages.pages['Extensions'].frame
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001324 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1325 self.ext_userCfg = idleConf.userCfg['extensions']
1326 self.is_int = self.register(is_int)
1327 self.load_extensions()
terryjreedye5bb1122017-07-05 00:54:55 -04001328 # Create widgets - a listbox shows all available extensions, with the
1329 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001330 self.extension_names = StringVar(self)
1331 frame.rowconfigure(0, weight=1)
1332 frame.columnconfigure(2, weight=1)
1333 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1334 selectmode='browse')
1335 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1336 scroll = Scrollbar(frame, command=self.extension_list.yview)
1337 self.extension_list.yscrollcommand=scroll.set
1338 self.details_frame = LabelFrame(frame, width=250, height=250)
1339 self.extension_list.grid(column=0, row=0, sticky='nws')
1340 scroll.grid(column=1, row=0, sticky='ns')
1341 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1342 frame.configure(padx=10, pady=10)
1343 self.config_frame = {}
1344 self.current_extension = None
1345
1346 self.outerframe = self # TEMPORARY
1347 self.tabbed_page_set = self.extension_list # TEMPORARY
1348
terryjreedye5bb1122017-07-05 00:54:55 -04001349 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001350 ext_names = ''
1351 for ext_name in sorted(self.extensions):
1352 self.create_extension_frame(ext_name)
1353 ext_names = ext_names + '{' + ext_name + '} '
1354 self.extension_names.set(ext_names)
1355 self.extension_list.selection_set(0)
1356 self.extension_selected(None)
1357
1358 def load_extensions(self):
1359 "Fill self.extensions with data from the default and user configs."
1360 self.extensions = {}
1361 for ext_name in idleConf.GetExtensions(active_only=False):
1362 self.extensions[ext_name] = []
1363
1364 for ext_name in self.extensions:
1365 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1366
terryjreedye5bb1122017-07-05 00:54:55 -04001367 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001368 enables = [opt_name for opt_name in opt_list
1369 if opt_name.startswith('enable')]
1370 for opt_name in enables:
1371 opt_list.remove(opt_name)
1372 opt_list = enables + opt_list
1373
1374 for opt_name in opt_list:
1375 def_str = self.ext_defaultCfg.Get(
1376 ext_name, opt_name, raw=True)
1377 try:
1378 def_obj = {'True':True, 'False':False}[def_str]
1379 opt_type = 'bool'
1380 except KeyError:
1381 try:
1382 def_obj = int(def_str)
1383 opt_type = 'int'
1384 except ValueError:
1385 def_obj = def_str
1386 opt_type = None
1387 try:
1388 value = self.ext_userCfg.Get(
1389 ext_name, opt_name, type=opt_type, raw=True,
1390 default=def_obj)
terryjreedye5bb1122017-07-05 00:54:55 -04001391 except ValueError: # Need this until .Get fixed.
1392 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001393 var = StringVar(self)
1394 var.set(str(value))
1395
1396 self.extensions[ext_name].append({'name': opt_name,
1397 'type': opt_type,
1398 'default': def_str,
1399 'value': value,
1400 'var': var,
1401 })
1402
1403 def extension_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001404 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001405 newsel = self.extension_list.curselection()
1406 if newsel:
1407 newsel = self.extension_list.get(newsel)
1408 if newsel is None or newsel != self.current_extension:
1409 if self.current_extension:
1410 self.details_frame.config(text='')
1411 self.config_frame[self.current_extension].grid_forget()
1412 self.current_extension = None
1413 if newsel:
1414 self.details_frame.config(text=newsel)
1415 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1416 self.current_extension = newsel
1417
1418 def create_extension_frame(self, ext_name):
1419 """Create a frame holding the widgets to configure one extension"""
1420 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1421 self.config_frame[ext_name] = f
1422 entry_area = f.interior
terryjreedye5bb1122017-07-05 00:54:55 -04001423 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001424 for row, opt in enumerate(self.extensions[ext_name]):
terryjreedye5bb1122017-07-05 00:54:55 -04001425 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001426 label = Label(entry_area, text=opt['name'])
1427 label.grid(row=row, column=0, sticky=NW)
1428 var = opt['var']
1429 if opt['type'] == 'bool':
1430 Checkbutton(entry_area, textvariable=var, variable=var,
1431 onvalue='True', offvalue='False',
1432 indicatoron=FALSE, selectcolor='', width=8
1433 ).grid(row=row, column=1, sticky=W, padx=7)
1434 elif opt['type'] == 'int':
1435 Entry(entry_area, textvariable=var, validate='key',
1436 validatecommand=(self.is_int, '%P')
1437 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1438
1439 else:
1440 Entry(entry_area, textvariable=var
1441 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1442 return
1443
1444 def set_extension_value(self, section, opt):
terryjreedye5bb1122017-07-05 00:54:55 -04001445 """Return True if the configuration was added or changed.
1446
1447 If the value is the same as the default, then remove it
1448 from user config file.
1449 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001450 name = opt['name']
1451 default = opt['default']
1452 value = opt['var'].get().strip() or default
1453 opt['var'].set(value)
1454 # if self.defaultCfg.has_section(section):
terryjreedye5bb1122017-07-05 00:54:55 -04001455 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001456 if (value == default):
1457 return self.ext_userCfg.RemoveOption(section, name)
terryjreedye5bb1122017-07-05 00:54:55 -04001458 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001459 return self.ext_userCfg.SetOption(section, name, value)
1460
1461 def save_all_changed_extensions(self):
1462 """Save configuration changes to the user config file."""
1463 has_changes = False
1464 for ext_name in self.extensions:
1465 options = self.extensions[ext_name]
1466 for opt in options:
1467 if self.set_extension_value(ext_name, opt):
1468 has_changes = True
1469 if has_changes:
1470 self.ext_userCfg.Save()
1471
1472
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001473help_common = '''\
1474When you click either the Apply or Ok buttons, settings in this
1475dialog that are different from IDLE's default are saved in
1476a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001477these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001478machine. Some do not take affect until IDLE is restarted.
1479[Cancel] only cancels changes made since the last save.
1480'''
1481help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001482 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001483Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001484The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001485be used with older IDLE releases if it is saved as a custom
1486theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001487''',
1488 'Keys': '''
1489Keys:
1490The IDLE Modern Unix key set is new in June 2016. It can only
1491be used with older IDLE releases if it is saved as a custom
1492key set, with a different name.
1493''',
terryjreedyaf683822017-06-27 23:02:19 -04001494 'Extensions': '''
1495Extensions:
1496
1497Autocomplete: Popupwait is milleseconds to wait after key char, without
1498cursor movement, before popping up completion box. Key char is '.' after
1499identifier or a '/' (or '\\' on Windows) within a string.
1500
1501FormatParagraph: Max-width is max chars in lines after re-formatting.
1502Use with paragraphs in both strings and comment blocks.
1503
1504ParenMatch: Style indicates what is highlighted when closer is entered:
1505'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1506'expression' (default) - also everything in between. Flash-delay is how
1507long to highlight if cursor is not moved (0 means forever).
1508'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001509}
1510
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001511
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001512def is_int(s):
1513 "Return 's is blank or represents an int'"
1514 if not s:
1515 return True
1516 try:
1517 int(s)
1518 return True
1519 except ValueError:
1520 return False
1521
1522
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001523class VerticalScrolledFrame(Frame):
1524 """A pure Tkinter vertically scrollable frame.
1525
1526 * Use the 'interior' attribute to place widgets inside the scrollable frame
1527 * Construct and pack/place/grid normally
1528 * This frame only allows vertical scrolling
1529 """
1530 def __init__(self, parent, *args, **kw):
1531 Frame.__init__(self, parent, *args, **kw)
1532
terryjreedye5bb1122017-07-05 00:54:55 -04001533 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001534 vscrollbar = Scrollbar(self, orient=VERTICAL)
1535 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1536 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001537 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001538 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1539 vscrollbar.config(command=canvas.yview)
1540
terryjreedye5bb1122017-07-05 00:54:55 -04001541 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001542 canvas.xview_moveto(0)
1543 canvas.yview_moveto(0)
1544
terryjreedye5bb1122017-07-05 00:54:55 -04001545 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001546 self.interior = interior = Frame(canvas)
1547 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1548
terryjreedye5bb1122017-07-05 00:54:55 -04001549 # Track changes to the canvas and frame width and sync them,
1550 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001551 def _configure_interior(event):
terryjreedye5bb1122017-07-05 00:54:55 -04001552 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001553 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1554 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001555 interior.bind('<Configure>', _configure_interior)
1556
1557 def _configure_canvas(event):
1558 if interior.winfo_reqwidth() != canvas.winfo_width():
terryjreedye5bb1122017-07-05 00:54:55 -04001559 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001560 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1561 canvas.bind('<Configure>', _configure_canvas)
1562
1563 return
1564
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001565
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00001566if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04001567 import unittest
1568 unittest.main('idlelib.idle_test.test_configdialog',
1569 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04001570 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04001571 run(ConfigDialog)