blob: 0d68b8081e380599e920cef89030cdf293ab85a5 [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 = {}
terryjreedy42abf7f2017-07-13 22:24:55 -040050 if not _utest:
51 self.withdraw()
Guido van Rossum8ce8a782007-11-01 19:42:39 +000052
Steven M. Gavad721c482001-07-31 10:46:53 +000053 self.configure(borderwidth=5)
Terry Jan Reedycd567362014-10-17 01:31:35 -040054 self.title(title or 'IDLE Preferences')
terryjreedy938e7382017-06-26 20:48:39 -040055 x = parent.winfo_rootx() + 20
56 y = parent.winfo_rooty() + (30 if not _htest else 150)
57 self.geometry(f'+{x}+{y}')
terryjreedye5bb1122017-07-05 00:54:55 -040058 # Each theme element key is its display name.
59 # The first value of the tuple is the sample area tag name.
60 # The second value is the display name list sort index.
terryjreedy938e7382017-06-26 20:48:39 -040061 self.theme_elements={
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -040062 'Normal Text': ('normal', '00'),
63 'Python Keywords': ('keyword', '01'),
64 'Python Definitions': ('definition', '02'),
65 'Python Builtins': ('builtin', '03'),
66 'Python Comments': ('comment', '04'),
67 'Python Strings': ('string', '05'),
68 'Selected Text': ('hilite', '06'),
69 'Found Text': ('hit', '07'),
70 'Cursor': ('cursor', '08'),
71 'Editor Breakpoint': ('break', '09'),
72 'Shell Normal Text': ('console', '10'),
73 'Shell Error Text': ('error', '11'),
74 'Shell Stdout Text': ('stdout', '12'),
75 'Shell Stderr Text': ('stderr', '13'),
Kurt B. Kaiser73360a32004-03-08 18:15:31 +000076 }
terryjreedy938e7382017-06-26 20:48:39 -040077 self.create_widgets()
Terry Jan Reedy4036d872014-08-03 23:02:58 -040078 self.resizable(height=FALSE, width=FALSE)
Steven M. Gavad721c482001-07-31 10:46:53 +000079 self.transient(parent)
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:
terryjreedy42abf7f2017-07-13 22:24:55 -040091 self.grab_set()
Terry Jan Reedycfa89502014-07-14 23:07:32 -040092 self.wm_deiconify()
93 self.wait_window()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000094
terryjreedy938e7382017-06-26 20:48:39 -040095 def create_widgets(self):
terryjreedye5bb1122017-07-05 00:54:55 -040096 "Create and place widgets for tabbed dialog."
terryjreedy938e7382017-06-26 20:48:39 -040097 self.tab_pages = TabbedPageSet(self,
Terry Jan Reedy93f35422015-10-13 22:03:51 -040098 page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
99 'Extensions'])
terryjreedy938e7382017-06-26 20:48:39 -0400100 self.tab_pages.pack(side=TOP, expand=TRUE, fill=BOTH)
101 self.create_page_font_tab()
102 self.create_page_highlight()
103 self.create_page_keys()
104 self.create_page_general()
105 self.create_page_extensions()
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400106 self.create_action_buttons().pack(side=BOTTOM)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400107
Terry Jan Reedy92cb0a32014-10-08 20:29:13 -0400108 def create_action_buttons(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400109 "Return frame of action buttons for dialog."
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400110 if macosx.isAquaTk():
Terry Jan Reedye3416e62014-07-26 19:40:16 -0400111 # Changing the default padding on OSX results in unreadable
terryjreedye5bb1122017-07-05 00:54:55 -0400112 # text in the buttons.
terryjreedy938e7382017-06-26 20:48:39 -0400113 padding_args = {}
Ronald Oussoren9e350042009-02-12 16:02:11 +0000114 else:
terryjreedy938e7382017-06-26 20:48:39 -0400115 padding_args = {'padx':6, 'pady':3}
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400116 outer = Frame(self, pady=2)
117 buttons = Frame(outer, pady=2)
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400118 for txt, cmd in (
terryjreedy938e7382017-06-26 20:48:39 -0400119 ('Ok', self.ok),
120 ('Apply', self.apply),
121 ('Cancel', self.cancel),
122 ('Help', self.help)):
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400123 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
terryjreedy938e7382017-06-26 20:48:39 -0400124 **padding_args).pack(side=LEFT, padx=5)
terryjreedye5bb1122017-07-05 00:54:55 -0400125 # Add space above buttons.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -0400126 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
127 buttons.pack(side=BOTTOM)
128 return outer
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -0400129
terryjreedy938e7382017-06-26 20:48:39 -0400130 def create_page_font_tab(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400131 """Return frame of widgets for Font/Tabs tab.
132
133 Configuration attributes:
134 font_size: Font size.
135 font_bold: Select font bold or not.
136 font_name: Font face.
137 space_num: Indentation width.
138 edit_font: Font widget with default font name, size, and weight.
139 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400140 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400141 self.font_size = StringVar(parent)
142 self.font_bold = BooleanVar(parent)
143 self.font_name = StringVar(parent)
144 self.space_num = IntVar(parent)
145 self.edit_font = tkFont.Font(parent, ('courier', 10, 'normal'))
Terry Jan Reedy22405332014-07-30 19:24:32 -0400146
terryjreedy7ab33422017-07-09 19:26:32 -0400147 # Create widgets.
148 # body and body section frames.
terryjreedy938e7382017-06-26 20:48:39 -0400149 frame = self.tab_pages.pages['Fonts/Tabs'].frame
terryjreedy938e7382017-06-26 20:48:39 -0400150 frame_font = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400151 frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
terryjreedy938e7382017-06-26 20:48:39 -0400152 frame_indent = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400153 frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
terryjreedy7ab33422017-07-09 19:26:32 -0400154 # frame_font
terryjreedy938e7382017-06-26 20:48:39 -0400155 frame_font_name = Frame(frame_font)
156 frame_font_param = Frame(frame_font)
157 font_name_title = Label(
158 frame_font_name, justify=LEFT, text='Font Face :')
terryjreedy7ab33422017-07-09 19:26:32 -0400159 self.fontlist = Listbox(
terryjreedy938e7382017-06-26 20:48:39 -0400160 frame_font_name, height=5, takefocus=FALSE, exportselection=FALSE)
terryjreedy953e5272017-07-11 02:16:41 -0400161 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
162 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
163 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
terryjreedy938e7382017-06-26 20:48:39 -0400164 scroll_font = Scrollbar(frame_font_name)
terryjreedy7ab33422017-07-09 19:26:32 -0400165 scroll_font.config(command=self.fontlist.yview)
166 self.fontlist.config(yscrollcommand=scroll_font.set)
terryjreedy938e7382017-06-26 20:48:39 -0400167 font_size_title = Label(frame_font_param, text='Size :')
168 self.opt_menu_font_size = DynOptionMenu(
169 frame_font_param, self.font_size, None, command=self.set_font_sample)
170 check_font_bold = Checkbutton(
171 frame_font_param, variable=self.font_bold, onvalue=1,
172 offvalue=0, text='Bold', command=self.set_font_sample)
173 frame_font_sample = Frame(frame_font, relief=SOLID, borderwidth=1)
174 self.font_sample = Label(
175 frame_font_sample, justify=LEFT, font=self.edit_font,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400176 text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
terryjreedy7ab33422017-07-09 19:26:32 -0400177 # frame_indent
terryjreedy938e7382017-06-26 20:48:39 -0400178 frame_indent_size = Frame(frame_indent)
179 indent_size_title = Label(
180 frame_indent_size, justify=LEFT,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400181 text='Python Standard: 4 Spaces!')
terryjreedy938e7382017-06-26 20:48:39 -0400182 self.scale_indent_size = Scale(
183 frame_indent_size, variable=self.space_num,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400184 orient='horizontal', tickinterval=2, from_=2, to=16)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400185
terryjreedy7ab33422017-07-09 19:26:32 -0400186 # Pack widgets.
187 # body
terryjreedy938e7382017-06-26 20:48:39 -0400188 frame_font.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
189 frame_indent.pack(side=LEFT, padx=5, pady=5, fill=Y)
terryjreedy7ab33422017-07-09 19:26:32 -0400190 # frame_font
terryjreedy938e7382017-06-26 20:48:39 -0400191 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
192 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
193 font_name_title.pack(side=TOP, anchor=W)
terryjreedy7ab33422017-07-09 19:26:32 -0400194 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
terryjreedy938e7382017-06-26 20:48:39 -0400195 scroll_font.pack(side=LEFT, fill=Y)
196 font_size_title.pack(side=LEFT, anchor=W)
197 self.opt_menu_font_size.pack(side=LEFT, anchor=W)
198 check_font_bold.pack(side=LEFT, anchor=W, padx=20)
199 frame_font_sample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
200 self.font_sample.pack(expand=TRUE, fill=BOTH)
terryjreedy7ab33422017-07-09 19:26:32 -0400201 # frame_indent
terryjreedy938e7382017-06-26 20:48:39 -0400202 frame_indent_size.pack(side=TOP, fill=X)
203 indent_size_title.pack(side=TOP, anchor=W, padx=5)
204 self.scale_indent_size.pack(side=TOP, padx=5, fill=X)
terryjreedy7ab33422017-07-09 19:26:32 -0400205
Steven M. Gava952d0a52001-08-03 04:43:44 +0000206 return frame
207
terryjreedy938e7382017-06-26 20:48:39 -0400208 def create_page_highlight(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400209 """Return frame of widgets for Highlighting tab.
210
211 Configuration attributes:
212 builtin_theme: Menu variable for built-in theme.
213 custom_theme: Menu variable for custom theme.
214 fg_bg_toggle: Toggle for foreground/background color.
215 colour: Color of selected target.
216 is_builtin_theme: Selector for built-in or custom theme.
217 highlight_target: Menu variable for the highlight tag target.
218 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400219 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400220 self.builtin_theme = StringVar(parent)
221 self.custom_theme = StringVar(parent)
222 self.fg_bg_toggle = BooleanVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400223 self.colour = StringVar(parent)
terryjreedy938e7382017-06-26 20:48:39 -0400224 self.is_builtin_theme = BooleanVar(parent)
225 self.highlight_target = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400226
Steven M. Gava952d0a52001-08-03 04:43:44 +0000227 ##widget creation
228 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400229 frame = self.tab_pages.pages['Highlighting'].frame
Steven M. Gava952d0a52001-08-03 04:43:44 +0000230 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400231 frame_custom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400232 text=' Custom Highlighting ')
terryjreedy938e7382017-06-26 20:48:39 -0400233 frame_theme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400234 text=' Highlighting Theme ')
terryjreedy938e7382017-06-26 20:48:39 -0400235 #frame_custom
236 self.text_highlight_sample=Text(
237 frame_custom, relief=SOLID, borderwidth=1,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400238 font=('courier', 12, ''), cursor='hand2', width=21, height=11,
239 takefocus=FALSE, highlightthickness=0, wrap=NONE)
terryjreedy938e7382017-06-26 20:48:39 -0400240 text=self.text_highlight_sample
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400241 text.bind('<Double-Button-1>', lambda e: 'break')
242 text.bind('<B1-Motion>', lambda e: 'break')
terryjreedy938e7382017-06-26 20:48:39 -0400243 text_and_tags=(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400244 ('#you can click here', 'comment'), ('\n', 'normal'),
245 ('#to choose items', 'comment'), ('\n', 'normal'),
246 ('def', 'keyword'), (' ', 'normal'),
247 ('func', 'definition'), ('(param):\n ', 'normal'),
248 ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
249 ("'string'", 'string'), ('\n var1 = ', 'normal'),
250 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
251 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
252 ('list', 'builtin'), ('(', 'normal'),
Terry Jan Reedya8aa4d52015-10-02 22:12:17 -0400253 ('None', 'keyword'), (')\n', 'normal'),
254 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400255 (' error ', 'error'), (' ', 'normal'),
256 ('cursor |', 'cursor'), ('\n ', 'normal'),
257 ('shell', 'console'), (' ', 'normal'),
258 ('stdout', 'stdout'), (' ', 'normal'),
259 ('stderr', 'stderr'), ('\n', 'normal'))
terryjreedy938e7382017-06-26 20:48:39 -0400260 for texttag in text_and_tags:
261 text.insert(END, texttag[0], texttag[1])
262 for element in self.theme_elements:
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400263 def tem(event, elem=element):
terryjreedy938e7382017-06-26 20:48:39 -0400264 event.widget.winfo_toplevel().highlight_target.set(elem)
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400265 text.tag_bind(
terryjreedy938e7382017-06-26 20:48:39 -0400266 self.theme_elements[element][0], '<ButtonPress-1>', tem)
Steven M. Gavae16d94b2001-11-03 05:07:28 +0000267 text.config(state=DISABLED)
terryjreedy938e7382017-06-26 20:48:39 -0400268 self.frame_colour_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
269 frame_fg_bg_toggle = Frame(frame_custom)
270 button_set_colour = Button(
271 self.frame_colour_set, text='Choose Colour for :',
272 command=self.get_colour, highlightthickness=0)
273 self.opt_menu_highlight_target = DynOptionMenu(
274 self.frame_colour_set, self.highlight_target, None,
275 highlightthickness=0) #, command=self.set_highlight_targetBinding
276 self.radio_fg = Radiobutton(
277 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
278 text='Foreground', command=self.set_colour_sample_binding)
279 self.radio_bg=Radiobutton(
280 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
281 text='Background', command=self.set_colour_sample_binding)
282 self.fg_bg_toggle.set(1)
283 button_save_custom_theme = Button(
284 frame_custom, text='Save as New Custom Theme',
285 command=self.save_as_new_theme)
286 #frame_theme
287 theme_type_title = Label(frame_theme, text='Select : ')
288 self.radio_theme_builtin = Radiobutton(
289 frame_theme, variable=self.is_builtin_theme, value=1,
290 command=self.set_theme_type, text='a Built-in Theme')
291 self.radio_theme_custom = Radiobutton(
292 frame_theme, variable=self.is_builtin_theme, value=0,
293 command=self.set_theme_type, text='a Custom Theme')
294 self.opt_menu_theme_builtin = DynOptionMenu(
295 frame_theme, self.builtin_theme, None, command=None)
296 self.opt_menu_theme_custom=DynOptionMenu(
297 frame_theme, self.custom_theme, None, command=None)
298 self.button_delete_custom_theme=Button(
299 frame_theme, text='Delete Custom Theme',
300 command=self.delete_custom_theme)
301 self.new_custom_theme = Label(frame_theme, bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400302
Steven M. Gava952d0a52001-08-03 04:43:44 +0000303 ##widget packing
304 #body
terryjreedy938e7382017-06-26 20:48:39 -0400305 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
306 frame_theme.pack(side=LEFT, padx=5, pady=5, fill=Y)
307 #frame_custom
308 self.frame_colour_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
309 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
310 self.text_highlight_sample.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400311 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
terryjreedy938e7382017-06-26 20:48:39 -0400312 button_set_colour.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
313 self.opt_menu_highlight_target.pack(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400314 side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
terryjreedy938e7382017-06-26 20:48:39 -0400315 self.radio_fg.pack(side=LEFT, anchor=E)
316 self.radio_bg.pack(side=RIGHT, anchor=W)
317 button_save_custom_theme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
318 #frame_theme
319 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
320 self.radio_theme_builtin.pack(side=TOP, anchor=W, padx=5)
321 self.radio_theme_custom.pack(side=TOP, anchor=W, padx=5, pady=2)
322 self.opt_menu_theme_builtin.pack(side=TOP, fill=X, padx=5, pady=5)
323 self.opt_menu_theme_custom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
324 self.button_delete_custom_theme.pack(side=TOP, fill=X, padx=5, pady=5)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500325 self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000326 return frame
327
terryjreedy938e7382017-06-26 20:48:39 -0400328 def create_page_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400329 """Return frame of widgets for Keys tab.
330
331 Configuration attributes:
332 builtin_keys: Menu variable for built-in keybindings.
333 custom_keys: Menu variable for custom keybindings.
334 are_keys_builtin: Selector for built-in or custom keybindings.
335 keybinding: Action/key bindings.
336 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400337 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400338 self.builtin_keys = StringVar(parent)
339 self.custom_keys = StringVar(parent)
340 self.are_keys_builtin = BooleanVar(parent)
341 self.keybinding = StringVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400342
Steven M. Gava60fc7072001-08-04 13:58:22 +0000343 ##widget creation
344 #body frame
terryjreedy938e7382017-06-26 20:48:39 -0400345 frame = self.tab_pages.pages['Keys'].frame
Steven M. Gava60fc7072001-08-04 13:58:22 +0000346 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400347 frame_custom = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400348 frame, borderwidth=2, relief=GROOVE,
349 text=' Custom Key Bindings ')
terryjreedy938e7382017-06-26 20:48:39 -0400350 frame_key_sets = LabelFrame(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400351 frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
terryjreedy938e7382017-06-26 20:48:39 -0400352 #frame_custom
353 frame_target = Frame(frame_custom)
354 target_title = Label(frame_target, text='Action - Key(s)')
355 scroll_target_y = Scrollbar(frame_target)
356 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
357 self.list_bindings = Listbox(
358 frame_target, takefocus=FALSE, exportselection=FALSE)
359 self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
360 scroll_target_y.config(command=self.list_bindings.yview)
361 scroll_target_x.config(command=self.list_bindings.xview)
362 self.list_bindings.config(yscrollcommand=scroll_target_y.set)
363 self.list_bindings.config(xscrollcommand=scroll_target_x.set)
364 self.button_new_keys = Button(
365 frame_custom, text='Get New Keys for Selection',
366 command=self.get_new_keys, state=DISABLED)
367 #frame_key_sets
368 frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
Christian Heimes9a371592007-12-28 14:08:13 +0000369 for i in range(2)]
terryjreedy938e7382017-06-26 20:48:39 -0400370 self.radio_keys_builtin = Radiobutton(
371 frames[0], variable=self.are_keys_builtin, value=1,
372 command=self.set_keys_type, text='Use a Built-in Key Set')
373 self.radio_keys_custom = Radiobutton(
374 frames[0], variable=self.are_keys_builtin, value=0,
375 command=self.set_keys_type, text='Use a Custom Key Set')
376 self.opt_menu_keys_builtin = DynOptionMenu(
377 frames[0], self.builtin_keys, None, command=None)
378 self.opt_menu_keys_custom = DynOptionMenu(
379 frames[0], self.custom_keys, None, command=None)
380 self.button_delete_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400381 frames[1], text='Delete Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400382 command=self.delete_custom_keys)
383 button_save_custom_keys = Button(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400384 frames[1], text='Save as New Custom Key Set',
terryjreedy938e7382017-06-26 20:48:39 -0400385 command=self.save_as_new_key_set)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400386 self.new_custom_keys = Label(frames[0], bd=2)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400387
Steven M. Gava60fc7072001-08-04 13:58:22 +0000388 ##widget packing
389 #body
terryjreedy938e7382017-06-26 20:48:39 -0400390 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
391 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
392 #frame_custom
393 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
394 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
Steven M. Gavafacfc092002-01-19 00:29:54 +0000395 #frame target
terryjreedy938e7382017-06-26 20:48:39 -0400396 frame_target.columnconfigure(0, weight=1)
397 frame_target.rowconfigure(1, weight=1)
398 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
399 self.list_bindings.grid(row=1, column=0, sticky=NSEW)
400 scroll_target_y.grid(row=1, column=1, sticky=NS)
401 scroll_target_x.grid(row=2, column=0, sticky=EW)
402 #frame_key_sets
403 self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
404 self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
405 self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
406 self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400407 self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
terryjreedy938e7382017-06-26 20:48:39 -0400408 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
409 button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
Christian Heimes9a371592007-12-28 14:08:13 +0000410 frames[0].pack(side=TOP, fill=BOTH, expand=True)
411 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000412 return frame
413
terryjreedy938e7382017-06-26 20:48:39 -0400414 def create_page_general(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400415 """Return frame of widgets for General tab.
416
417 Configuration attributes:
418 win_width: Initial window width in characters.
419 win_height: Initial window height in characters.
420 startup_edit: Selector for opening in editor or shell mode.
421 autosave: Selector for save prompt popup when using Run.
terryjreedye5bb1122017-07-05 00:54:55 -0400422 """
Terry Jan Reedy22405332014-07-30 19:24:32 -0400423 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -0400424 self.win_width = StringVar(parent)
425 self.win_height = StringVar(parent)
426 self.startup_edit = IntVar(parent)
427 self.autosave = IntVar(parent)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400428
Steven M. Gava230e5782001-08-07 03:28:25 +0000429 #widget creation
430 #body
terryjreedy938e7382017-06-26 20:48:39 -0400431 frame = self.tab_pages.pages['General'].frame
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000432 #body section frames
terryjreedy938e7382017-06-26 20:48:39 -0400433 frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400434 text=' Startup Preferences ')
terryjreedy938e7382017-06-26 20:48:39 -0400435 frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
436 text=' autosave Preferences ')
437 frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
438 frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400439 text=' Additional Help Sources ')
terryjreedy938e7382017-06-26 20:48:39 -0400440 #frame_run
441 startup_title = Label(frame_run, text='At Startup')
442 self.radio_startup_edit = Radiobutton(
443 frame_run, variable=self.startup_edit, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500444 text="Open Edit Window")
terryjreedy938e7382017-06-26 20:48:39 -0400445 self.radio_startup_shell = Radiobutton(
446 frame_run, variable=self.startup_edit, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500447 text='Open Shell Window')
terryjreedy938e7382017-06-26 20:48:39 -0400448 #frame_save
449 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
450 self.radio_save_ask = Radiobutton(
451 frame_save, variable=self.autosave, value=0,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500452 text="Prompt to Save")
terryjreedy938e7382017-06-26 20:48:39 -0400453 self.radio_save_auto = Radiobutton(
454 frame_save, variable=self.autosave, value=1,
Terry Jan Reedyf46b7822016-11-07 17:15:01 -0500455 text='No Prompt')
terryjreedy938e7382017-06-26 20:48:39 -0400456 #frame_win_size
457 win_size_title = Label(
458 frame_win_size, text='Initial Window Size (in characters)')
459 win_width_title = Label(frame_win_size, text='Width')
460 self.entry_win_width = Entry(
461 frame_win_size, textvariable=self.win_width, width=3)
462 win_height_title = Label(frame_win_size, text='Height')
463 self.entry_win_height = Entry(
464 frame_win_size, textvariable=self.win_height, width=3)
465 #frame_help
466 frame_helplist = Frame(frame_help)
467 frame_helplist_buttons = Frame(frame_helplist)
468 scroll_helplist = Scrollbar(frame_helplist)
469 self.list_help = Listbox(
470 frame_helplist, height=5, takefocus=FALSE,
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000471 exportselection=FALSE)
terryjreedy938e7382017-06-26 20:48:39 -0400472 scroll_helplist.config(command=self.list_help.yview)
473 self.list_help.config(yscrollcommand=scroll_helplist.set)
474 self.list_help.bind('<ButtonRelease-1>', self.help_source_selected)
475 self.button_helplist_edit = Button(
476 frame_helplist_buttons, text='Edit', state=DISABLED,
477 width=8, command=self.helplist_item_edit)
478 self.button_helplist_add = Button(
479 frame_helplist_buttons, text='Add',
480 width=8, command=self.helplist_item_add)
481 self.button_helplist_remove = Button(
482 frame_helplist_buttons, text='Remove', state=DISABLED,
483 width=8, command=self.helplist_item_remove)
Terry Jan Reedy22405332014-07-30 19:24:32 -0400484
Steven M. Gava230e5782001-08-07 03:28:25 +0000485 #widget packing
486 #body
terryjreedy938e7382017-06-26 20:48:39 -0400487 frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
488 frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
489 frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
490 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
491 #frame_run
492 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
493 self.radio_startup_shell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
494 self.radio_startup_edit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
495 #frame_save
496 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
497 self.radio_save_auto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
498 self.radio_save_ask.pack(side=RIGHT, anchor=W, padx=5, pady=5)
499 #frame_win_size
500 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
501 self.entry_win_height.pack(side=RIGHT, anchor=E, padx=10, pady=5)
502 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
503 self.entry_win_width.pack(side=RIGHT, anchor=E, padx=10, pady=5)
504 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
505 #frame_help
506 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
507 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
508 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
509 self.list_help.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
510 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
511 self.button_helplist_add.pack(side=TOP, anchor=W)
512 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
Steven M. Gava952d0a52001-08-03 04:43:44 +0000513 return frame
514
terryjreedy938e7382017-06-26 20:48:39 -0400515 def attach_var_callbacks(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400516 "Attach callbacks to variables that can be changed."
terryjreedy938e7382017-06-26 20:48:39 -0400517 self.font_size.trace_add('write', self.var_changed_font)
518 self.font_name.trace_add('write', self.var_changed_font)
519 self.font_bold.trace_add('write', self.var_changed_font)
520 self.space_num.trace_add('write', self.var_changed_space_num)
521 self.colour.trace_add('write', self.var_changed_colour)
522 self.builtin_theme.trace_add('write', self.var_changed_builtin_theme)
523 self.custom_theme.trace_add('write', self.var_changed_custom_theme)
524 self.is_builtin_theme.trace_add('write', self.var_changed_is_builtin_theme)
525 self.highlight_target.trace_add('write', self.var_changed_highlight_target)
526 self.keybinding.trace_add('write', self.var_changed_keybinding)
527 self.builtin_keys.trace_add('write', self.var_changed_builtin_keys)
528 self.custom_keys.trace_add('write', self.var_changed_custom_keys)
529 self.are_keys_builtin.trace_add('write', self.var_changed_are_keys_builtin)
530 self.win_width.trace_add('write', self.var_changed_win_width)
531 self.win_height.trace_add('write', self.var_changed_win_height)
532 self.startup_edit.trace_add('write', self.var_changed_startup_edit)
533 self.autosave.trace_add('write', self.var_changed_autosave)
Steven M. Gava052937f2002-02-11 02:20:53 +0000534
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400535 def remove_var_callbacks(self):
536 "Remove callbacks to prevent memory leaks."
537 for var in (
terryjreedy938e7382017-06-26 20:48:39 -0400538 self.font_size, self.font_name, self.font_bold,
539 self.space_num, self.colour, self.builtin_theme,
540 self.custom_theme, self.is_builtin_theme, self.highlight_target,
541 self.keybinding, self.builtin_keys, self.custom_keys,
542 self.are_keys_builtin, self.win_width, self.win_height,
terryjreedy8e3f73e2017-07-10 15:11:45 -0400543 self.startup_edit, self.autosave,):
Serhiy Storchaka81221742016-06-26 09:46:57 +0300544 var.trace_remove('write', var.trace_info()[0][1])
Terry Jan Reedy6b98ce22016-05-16 22:27:28 -0400545
terryjreedy938e7382017-06-26 20:48:39 -0400546 def var_changed_font(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400547 """Store changes to font attributes.
548
549 When one font attribute changes, save them all, as they are
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400550 not independent from each other. In particular, when we are
551 overriding the default font, we need to write out everything.
terryjreedye5bb1122017-07-05 00:54:55 -0400552 """
terryjreedy938e7382017-06-26 20:48:39 -0400553 value = self.font_name.get()
terryjreedyedc03422017-07-07 16:37:39 -0400554 changes.add_option('main', 'EditorWindow', 'font', value)
terryjreedy938e7382017-06-26 20:48:39 -0400555 value = self.font_size.get()
terryjreedyedc03422017-07-07 16:37:39 -0400556 changes.add_option('main', 'EditorWindow', 'font-size', value)
terryjreedy938e7382017-06-26 20:48:39 -0400557 value = self.font_bold.get()
terryjreedyedc03422017-07-07 16:37:39 -0400558 changes.add_option('main', 'EditorWindow', 'font-bold', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000559
terryjreedy938e7382017-06-26 20:48:39 -0400560 def var_changed_space_num(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400561 "Store change to indentation size."
terryjreedy938e7382017-06-26 20:48:39 -0400562 value = self.space_num.get()
terryjreedyedc03422017-07-07 16:37:39 -0400563 changes.add_option('main', 'Indent', 'num-spaces', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000564
terryjreedy938e7382017-06-26 20:48:39 -0400565 def var_changed_colour(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400566 "Process change to color choice."
terryjreedy938e7382017-06-26 20:48:39 -0400567 self.on_new_colour_set()
Steven M. Gava052937f2002-02-11 02:20:53 +0000568
terryjreedy938e7382017-06-26 20:48:39 -0400569 def var_changed_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400570 """Process new builtin theme selection.
571
572 Add the changed theme's name to the changed_items and recreate
573 the sample with the values from the selected theme.
574 """
terryjreedy938e7382017-06-26 20:48:39 -0400575 old_themes = ('IDLE Classic', 'IDLE New')
576 value = self.builtin_theme.get()
577 if value not in old_themes:
578 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
terryjreedyedc03422017-07-07 16:37:39 -0400579 changes.add_option('main', 'Theme', 'name', old_themes[0])
580 changes.add_option('main', 'Theme', 'name2', value)
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500581 self.new_custom_theme.config(text='New theme, see Help',
582 fg='#500000')
583 else:
terryjreedyedc03422017-07-07 16:37:39 -0400584 changes.add_option('main', 'Theme', 'name', value)
585 changes.add_option('main', 'Theme', 'name2', '')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500586 self.new_custom_theme.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400587 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000588
terryjreedy938e7382017-06-26 20:48:39 -0400589 def var_changed_custom_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400590 """Process new custom theme selection.
591
592 If a new custom theme is selected, add the name to the
593 changed_items and apply the theme to the sample.
594 """
terryjreedy938e7382017-06-26 20:48:39 -0400595 value = self.custom_theme.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000596 if value != '- no custom themes -':
terryjreedyedc03422017-07-07 16:37:39 -0400597 changes.add_option('main', 'Theme', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400598 self.paint_theme_sample()
Steven M. Gava052937f2002-02-11 02:20:53 +0000599
terryjreedy938e7382017-06-26 20:48:39 -0400600 def var_changed_is_builtin_theme(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400601 """Process toggle between builtin and custom theme.
602
603 Update the default toggle value and apply the newly
604 selected theme type.
605 """
terryjreedy938e7382017-06-26 20:48:39 -0400606 value = self.is_builtin_theme.get()
terryjreedyedc03422017-07-07 16:37:39 -0400607 changes.add_option('main', 'Theme', 'default', value)
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000608 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400609 self.var_changed_builtin_theme()
Steven M. Gavaf31eec02002-03-05 00:25:58 +0000610 else:
terryjreedy938e7382017-06-26 20:48:39 -0400611 self.var_changed_custom_theme()
Steven M. Gava052937f2002-02-11 02:20:53 +0000612
terryjreedy938e7382017-06-26 20:48:39 -0400613 def var_changed_highlight_target(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400614 "Process selection of new target tag for highlighting."
terryjreedy938e7382017-06-26 20:48:39 -0400615 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000616
terryjreedy938e7382017-06-26 20:48:39 -0400617 def var_changed_keybinding(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400618 "Store change to a keybinding."
terryjreedy938e7382017-06-26 20:48:39 -0400619 value = self.keybinding.get()
620 key_set = self.custom_keys.get()
621 event = self.list_bindings.get(ANCHOR).split()[0]
Steven M. Gavaa498af22002-02-01 01:33:36 +0000622 if idleConf.IsCoreBinding(event):
terryjreedyedc03422017-07-07 16:37:39 -0400623 changes.add_option('keys', key_set, event, value)
terryjreedye5bb1122017-07-05 00:54:55 -0400624 else: # Event is an extension binding.
terryjreedy938e7382017-06-26 20:48:39 -0400625 ext_name = idleConf.GetExtnNameForEvent(event)
626 ext_keybind_section = ext_name + '_cfgBindings'
terryjreedyedc03422017-07-07 16:37:39 -0400627 changes.add_option('extensions', ext_keybind_section, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000628
terryjreedy938e7382017-06-26 20:48:39 -0400629 def var_changed_builtin_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400630 "Process selection of builtin key set."
terryjreedy938e7382017-06-26 20:48:39 -0400631 old_keys = (
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400632 'IDLE Classic Windows',
633 'IDLE Classic Unix',
634 'IDLE Classic Mac',
635 'IDLE Classic OSX',
636 )
terryjreedy938e7382017-06-26 20:48:39 -0400637 value = self.builtin_keys.get()
638 if value not in old_keys:
639 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
terryjreedyedc03422017-07-07 16:37:39 -0400640 changes.add_option('main', 'Keys', 'name', old_keys[0])
641 changes.add_option('main', 'Keys', 'name2', value)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400642 self.new_custom_keys.config(text='New key set, see Help',
643 fg='#500000')
644 else:
terryjreedyedc03422017-07-07 16:37:39 -0400645 changes.add_option('main', 'Keys', 'name', value)
646 changes.add_option('main', 'Keys', 'name2', '')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400647 self.new_custom_keys.config(text='', fg='black')
terryjreedy938e7382017-06-26 20:48:39 -0400648 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000649
terryjreedy938e7382017-06-26 20:48:39 -0400650 def var_changed_custom_keys(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400651 "Process selection of custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400652 value = self.custom_keys.get()
Steven M. Gava49745752002-02-18 01:43:11 +0000653 if value != '- no custom keys -':
terryjreedyedc03422017-07-07 16:37:39 -0400654 changes.add_option('main', 'Keys', 'name', value)
terryjreedy938e7382017-06-26 20:48:39 -0400655 self.load_keys_list(value)
Steven M. Gava052937f2002-02-11 02:20:53 +0000656
terryjreedy938e7382017-06-26 20:48:39 -0400657 def var_changed_are_keys_builtin(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400658 "Process toggle between builtin key set and custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400659 value = self.are_keys_builtin.get()
terryjreedyedc03422017-07-07 16:37:39 -0400660 changes.add_option('main', 'Keys', 'default', value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000661 if value:
terryjreedy938e7382017-06-26 20:48:39 -0400662 self.var_changed_builtin_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000663 else:
terryjreedy938e7382017-06-26 20:48:39 -0400664 self.var_changed_custom_keys()
Steven M. Gava052937f2002-02-11 02:20:53 +0000665
terryjreedy938e7382017-06-26 20:48:39 -0400666 def var_changed_win_width(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400667 "Store change to window width."
terryjreedy938e7382017-06-26 20:48:39 -0400668 value = self.win_width.get()
terryjreedyedc03422017-07-07 16:37:39 -0400669 changes.add_option('main', 'EditorWindow', 'width', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000670
terryjreedy938e7382017-06-26 20:48:39 -0400671 def var_changed_win_height(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400672 "Store change to window height."
terryjreedy938e7382017-06-26 20:48:39 -0400673 value = self.win_height.get()
terryjreedyedc03422017-07-07 16:37:39 -0400674 changes.add_option('main', 'EditorWindow', 'height', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000675
terryjreedy938e7382017-06-26 20:48:39 -0400676 def var_changed_startup_edit(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400677 "Store change to toggle for starting IDLE in the editor or shell."
terryjreedy938e7382017-06-26 20:48:39 -0400678 value = self.startup_edit.get()
terryjreedyedc03422017-07-07 16:37:39 -0400679 changes.add_option('main', 'General', 'editor-on-startup', value)
Steven M. Gavac112cd82002-01-22 05:56:40 +0000680
terryjreedy938e7382017-06-26 20:48:39 -0400681 def var_changed_autosave(self, *params):
terryjreedye5bb1122017-07-05 00:54:55 -0400682 "Store change to autosave."
terryjreedy938e7382017-06-26 20:48:39 -0400683 value = self.autosave.get()
terryjreedyedc03422017-07-07 16:37:39 -0400684 changes.add_option('main', 'General', 'autosave', value)
Kurt B. Kaiser6c638b62003-05-26 06:23:10 +0000685
terryjreedy938e7382017-06-26 20:48:39 -0400686 def set_theme_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400687 "Set available screen options based on builtin or custom theme."
terryjreedy938e7382017-06-26 20:48:39 -0400688 if self.is_builtin_theme.get():
689 self.opt_menu_theme_builtin.config(state=NORMAL)
690 self.opt_menu_theme_custom.config(state=DISABLED)
691 self.button_delete_custom_theme.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000692 else:
terryjreedy938e7382017-06-26 20:48:39 -0400693 self.opt_menu_theme_builtin.config(state=DISABLED)
694 self.radio_theme_custom.config(state=NORMAL)
695 self.opt_menu_theme_custom.config(state=NORMAL)
696 self.button_delete_custom_theme.config(state=NORMAL)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000697
terryjreedy938e7382017-06-26 20:48:39 -0400698 def set_keys_type(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400699 "Set available screen options based on builtin or custom key set."
terryjreedy938e7382017-06-26 20:48:39 -0400700 if self.are_keys_builtin.get():
701 self.opt_menu_keys_builtin.config(state=NORMAL)
702 self.opt_menu_keys_custom.config(state=DISABLED)
703 self.button_delete_custom_keys.config(state=DISABLED)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000704 else:
terryjreedy938e7382017-06-26 20:48:39 -0400705 self.opt_menu_keys_builtin.config(state=DISABLED)
706 self.radio_keys_custom.config(state=NORMAL)
707 self.opt_menu_keys_custom.config(state=NORMAL)
708 self.button_delete_custom_keys.config(state=NORMAL)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000709
terryjreedy938e7382017-06-26 20:48:39 -0400710 def get_new_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400711 """Handle event to change key binding for selected line.
712
713 A selection of a key/binding in the list of current
714 bindings pops up a dialog to enter a new binding. If
715 the current key set is builtin and a binding has
716 changed, then a name for a custom key set needs to be
717 entered for the change to be applied.
718 """
terryjreedy938e7382017-06-26 20:48:39 -0400719 list_index = self.list_bindings.index(ANCHOR)
720 binding = self.list_bindings.get(list_index)
terryjreedye5bb1122017-07-05 00:54:55 -0400721 bind_name = binding.split()[0]
terryjreedy938e7382017-06-26 20:48:39 -0400722 if self.are_keys_builtin.get():
723 current_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000724 else:
terryjreedy938e7382017-06-26 20:48:39 -0400725 current_key_set_name = self.custom_keys.get()
726 current_bindings = idleConf.GetCurrentKeySet()
terryjreedyedc03422017-07-07 16:37:39 -0400727 if current_key_set_name in changes['keys']: # unsaved changes
728 key_set_changes = changes['keys'][current_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400729 for event in key_set_changes:
730 current_bindings[event] = key_set_changes[event].split()
731 current_key_sequences = list(current_bindings.values())
732 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
733 current_key_sequences).result
terryjreedye5bb1122017-07-05 00:54:55 -0400734 if new_keys:
735 if self.are_keys_builtin.get(): # Current key set is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400736 message = ('Your changes will be saved as a new Custom Key Set.'
737 ' Enter a name for your new Custom Key Set below.')
terryjreedy938e7382017-06-26 20:48:39 -0400738 new_keyset = self.get_new_keys_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400739 if not new_keyset: # User cancelled custom key set creation.
terryjreedy938e7382017-06-26 20:48:39 -0400740 self.list_bindings.select_set(list_index)
741 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000742 return
terryjreedye5bb1122017-07-05 00:54:55 -0400743 else: # Create new custom key set based on previously active key set.
terryjreedy938e7382017-06-26 20:48:39 -0400744 self.create_new_key_set(new_keyset)
745 self.list_bindings.delete(list_index)
746 self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
747 self.list_bindings.select_set(list_index)
748 self.list_bindings.select_anchor(list_index)
749 self.keybinding.set(new_keys)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000750 else:
terryjreedy938e7382017-06-26 20:48:39 -0400751 self.list_bindings.select_set(list_index)
752 self.list_bindings.select_anchor(list_index)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000753
terryjreedy938e7382017-06-26 20:48:39 -0400754 def get_new_keys_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400755 "Return new key set name from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400756 used_names = (idleConf.GetSectionList('user', 'keys') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400757 idleConf.GetSectionList('default', 'keys'))
terryjreedy938e7382017-06-26 20:48:39 -0400758 new_keyset = SectionName(
759 self, 'New Custom Key Set', message, used_names).result
760 return new_keyset
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000761
terryjreedy938e7382017-06-26 20:48:39 -0400762 def save_as_new_key_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400763 "Prompt for name of new key set and save changes using that name."
terryjreedy938e7382017-06-26 20:48:39 -0400764 new_keys_name = self.get_new_keys_name('New Key Set Name:')
765 if new_keys_name:
766 self.create_new_key_set(new_keys_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000767
terryjreedy938e7382017-06-26 20:48:39 -0400768 def keybinding_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -0400769 "Activate button to assign new keys to selected action."
terryjreedy938e7382017-06-26 20:48:39 -0400770 self.button_new_keys.config(state=NORMAL)
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000771
terryjreedy938e7382017-06-26 20:48:39 -0400772 def create_new_key_set(self, new_key_set_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400773 """Create a new custom key set with the given name.
774
775 Create the new key set based on the previously active set
776 with the current changes applied. Once it is saved, then
777 activate the new key set.
778 """
terryjreedy938e7382017-06-26 20:48:39 -0400779 if self.are_keys_builtin.get():
780 prev_key_set_name = self.builtin_keys.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000781 else:
terryjreedy938e7382017-06-26 20:48:39 -0400782 prev_key_set_name = self.custom_keys.get()
783 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
784 new_keys = {}
terryjreedye5bb1122017-07-05 00:54:55 -0400785 for event in prev_keys: # Add key set to changed items.
786 event_name = event[2:-2] # Trim off the angle brackets.
terryjreedy938e7382017-06-26 20:48:39 -0400787 binding = ' '.join(prev_keys[event])
788 new_keys[event_name] = binding
terryjreedye5bb1122017-07-05 00:54:55 -0400789 # Handle any unsaved changes to prev key set.
terryjreedyedc03422017-07-07 16:37:39 -0400790 if prev_key_set_name in changes['keys']:
791 key_set_changes = changes['keys'][prev_key_set_name]
terryjreedy938e7382017-06-26 20:48:39 -0400792 for event in key_set_changes:
793 new_keys[event] = key_set_changes[event]
terryjreedye5bb1122017-07-05 00:54:55 -0400794 # Save the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400795 self.save_new_key_set(new_key_set_name, new_keys)
terryjreedye5bb1122017-07-05 00:54:55 -0400796 # Change GUI over to the new key set.
terryjreedy938e7382017-06-26 20:48:39 -0400797 custom_key_list = idleConf.GetSectionList('user', 'keys')
798 custom_key_list.sort()
799 self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
800 self.are_keys_builtin.set(0)
801 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000802
terryjreedy938e7382017-06-26 20:48:39 -0400803 def load_keys_list(self, keyset_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400804 """Reload the list of action/key binding pairs for the active key set.
805
806 An action/key binding can be selected to change the key binding.
807 """
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400808 reselect = 0
terryjreedy938e7382017-06-26 20:48:39 -0400809 if self.list_bindings.curselection():
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400810 reselect = 1
terryjreedy938e7382017-06-26 20:48:39 -0400811 list_index = self.list_bindings.index(ANCHOR)
812 keyset = idleConf.GetKeySet(keyset_name)
813 bind_names = list(keyset.keys())
814 bind_names.sort()
815 self.list_bindings.delete(0, END)
816 for bind_name in bind_names:
terryjreedye5bb1122017-07-05 00:54:55 -0400817 key = ' '.join(keyset[bind_name])
818 bind_name = bind_name[2:-2] # Trim off the angle brackets.
terryjreedyedc03422017-07-07 16:37:39 -0400819 if keyset_name in changes['keys']:
terryjreedye5bb1122017-07-05 00:54:55 -0400820 # Handle any unsaved changes to this key set.
terryjreedyedc03422017-07-07 16:37:39 -0400821 if bind_name in changes['keys'][keyset_name]:
822 key = changes['keys'][keyset_name][bind_name]
terryjreedy938e7382017-06-26 20:48:39 -0400823 self.list_bindings.insert(END, bind_name+' - '+key)
Steven M. Gava052937f2002-02-11 02:20:53 +0000824 if reselect:
terryjreedy938e7382017-06-26 20:48:39 -0400825 self.list_bindings.see(list_index)
826 self.list_bindings.select_set(list_index)
827 self.list_bindings.select_anchor(list_index)
Steven M. Gava052937f2002-02-11 02:20:53 +0000828
terryjreedy938e7382017-06-26 20:48:39 -0400829 def delete_custom_keys(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400830 """Handle event to delete a custom key set.
831
832 Applying the delete deactivates the current configuration and
833 reverts to the default. The custom key set is permanently
834 deleted from the config file.
835 """
terryjreedy938e7382017-06-26 20:48:39 -0400836 keyset_name=self.custom_keys.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400837 delmsg = 'Are you sure you wish to delete the key set %r ?'
838 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400839 'Delete Key Set', delmsg % keyset_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000840 return
terryjreedy938e7382017-06-26 20:48:39 -0400841 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400842 # Remove key set from changes, config, and file.
terryjreedyc0179482017-07-11 19:50:10 -0400843 changes.delete_section('keys', keyset_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400844 # Reload user key set list.
terryjreedy938e7382017-06-26 20:48:39 -0400845 item_list = idleConf.GetSectionList('user', 'keys')
846 item_list.sort()
847 if not item_list:
848 self.radio_keys_custom.config(state=DISABLED)
849 self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
Steven M. Gava49745752002-02-18 01:43:11 +0000850 else:
terryjreedy938e7382017-06-26 20:48:39 -0400851 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400852 # Revert to default key set.
terryjreedy938e7382017-06-26 20:48:39 -0400853 self.are_keys_builtin.set(idleConf.defaultCfg['main']
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400854 .Get('Keys', 'default'))
terryjreedy938e7382017-06-26 20:48:39 -0400855 self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400856 or idleConf.default_keys())
terryjreedye5bb1122017-07-05 00:54:55 -0400857 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400858 changes.save_all()
859 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400860 self.activate_config_changes()
861 self.set_keys_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000862
terryjreedy938e7382017-06-26 20:48:39 -0400863 def delete_custom_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400864 """Handle event to delete custom theme.
865
866 The current theme is deactivated and the default theme is
867 activated. The custom theme is permanently removed from
868 the config file.
869 """
terryjreedy938e7382017-06-26 20:48:39 -0400870 theme_name = self.custom_theme.get()
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400871 delmsg = 'Are you sure you wish to delete the theme %r ?'
872 if not tkMessageBox.askyesno(
terryjreedy938e7382017-06-26 20:48:39 -0400873 'Delete Theme', delmsg % theme_name, parent=self):
Steven M. Gava49745752002-02-18 01:43:11 +0000874 return
terryjreedy938e7382017-06-26 20:48:39 -0400875 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -0400876 # Remove theme from changes, config, and file.
terryjreedyc0179482017-07-11 19:50:10 -0400877 changes.delete_section('highlight', theme_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400878 # Reload user theme list.
terryjreedy938e7382017-06-26 20:48:39 -0400879 item_list = idleConf.GetSectionList('user', 'highlight')
880 item_list.sort()
881 if not item_list:
882 self.radio_theme_custom.config(state=DISABLED)
883 self.opt_menu_theme_custom.SetMenu(item_list, '- no custom themes -')
Steven M. Gava49745752002-02-18 01:43:11 +0000884 else:
terryjreedy938e7382017-06-26 20:48:39 -0400885 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -0400886 # Revert to default theme.
terryjreedy938e7382017-06-26 20:48:39 -0400887 self.is_builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
888 self.builtin_theme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
terryjreedye5bb1122017-07-05 00:54:55 -0400889 # User can't back out of these changes, they must be applied now.
terryjreedyedc03422017-07-07 16:37:39 -0400890 changes.save_all()
891 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -0400892 self.activate_config_changes()
893 self.set_theme_type()
Steven M. Gava49745752002-02-18 01:43:11 +0000894
terryjreedy938e7382017-06-26 20:48:39 -0400895 def get_colour(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400896 """Handle button to select a new color for the target tag.
897
898 If a new color is selected while using a builtin theme, a
899 name must be supplied to create a custom theme.
900 """
terryjreedy938e7382017-06-26 20:48:39 -0400901 target = self.highlight_target.get()
902 prev_colour = self.frame_colour_set.cget('bg')
903 rgbTuplet, colour_string = tkColorChooser.askcolor(
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400904 parent=self, title='Pick new colour for : '+target,
terryjreedy938e7382017-06-26 20:48:39 -0400905 initialcolor=prev_colour)
906 if colour_string and (colour_string != prev_colour):
terryjreedye5bb1122017-07-05 00:54:55 -0400907 # User didn't cancel and they chose a new colour.
908 if self.is_builtin_theme.get(): # Current theme is a built-in.
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400909 message = ('Your changes will be saved as a new Custom Theme. '
910 'Enter a name for your new Custom Theme below.')
terryjreedy938e7382017-06-26 20:48:39 -0400911 new_theme = self.get_new_theme_name(message)
terryjreedye5bb1122017-07-05 00:54:55 -0400912 if not new_theme: # User cancelled custom theme creation.
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000913 return
terryjreedye5bb1122017-07-05 00:54:55 -0400914 else: # Create new custom theme based on previously active theme.
terryjreedy938e7382017-06-26 20:48:39 -0400915 self.create_new_theme(new_theme)
916 self.colour.set(colour_string)
terryjreedye5bb1122017-07-05 00:54:55 -0400917 else: # Current theme is user defined.
terryjreedy938e7382017-06-26 20:48:39 -0400918 self.colour.set(colour_string)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000919
terryjreedy938e7382017-06-26 20:48:39 -0400920 def on_new_colour_set(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400921 "Display sample of new color selection on the dialog."
terryjreedy938e7382017-06-26 20:48:39 -0400922 new_colour=self.colour.get()
terryjreedye5bb1122017-07-05 00:54:55 -0400923 self.frame_colour_set.config(bg=new_colour) # Set sample.
terryjreedy938e7382017-06-26 20:48:39 -0400924 plane ='foreground' if self.fg_bg_toggle.get() else 'background'
925 sample_element = self.theme_elements[self.highlight_target.get()][0]
926 self.text_highlight_sample.tag_config(sample_element, **{plane:new_colour})
927 theme = self.custom_theme.get()
928 theme_element = sample_element + '-' + plane
terryjreedyedc03422017-07-07 16:37:39 -0400929 changes.add_option('highlight', theme, theme_element, new_colour)
Steven M. Gava052937f2002-02-11 02:20:53 +0000930
terryjreedy938e7382017-06-26 20:48:39 -0400931 def get_new_theme_name(self, message):
terryjreedye5bb1122017-07-05 00:54:55 -0400932 "Return name of new theme from query popup."
terryjreedy938e7382017-06-26 20:48:39 -0400933 used_names = (idleConf.GetSectionList('user', 'highlight') +
Terry Jan Reedy4036d872014-08-03 23:02:58 -0400934 idleConf.GetSectionList('default', 'highlight'))
terryjreedy938e7382017-06-26 20:48:39 -0400935 new_theme = SectionName(
936 self, 'New Custom Theme', message, used_names).result
937 return new_theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000938
terryjreedy938e7382017-06-26 20:48:39 -0400939 def save_as_new_theme(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400940 "Prompt for new theme name and create the theme."
terryjreedy938e7382017-06-26 20:48:39 -0400941 new_theme_name = self.get_new_theme_name('New Theme Name:')
942 if new_theme_name:
943 self.create_new_theme(new_theme_name)
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000944
terryjreedy938e7382017-06-26 20:48:39 -0400945 def create_new_theme(self, new_theme_name):
terryjreedye5bb1122017-07-05 00:54:55 -0400946 """Create a new custom theme with the given name.
947
948 Create the new theme based on the previously active theme
949 with the current changes applied. Once it is saved, then
950 activate the new theme.
951 """
terryjreedy938e7382017-06-26 20:48:39 -0400952 if self.is_builtin_theme.get():
953 theme_type = 'default'
954 theme_name = self.builtin_theme.get()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000955 else:
terryjreedy938e7382017-06-26 20:48:39 -0400956 theme_type = 'user'
957 theme_name = self.custom_theme.get()
958 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
terryjreedye5bb1122017-07-05 00:54:55 -0400959 # Apply any of the old theme's unsaved changes to the new theme.
terryjreedyedc03422017-07-07 16:37:39 -0400960 if theme_name in changes['highlight']:
961 theme_changes = changes['highlight'][theme_name]
terryjreedy938e7382017-06-26 20:48:39 -0400962 for element in theme_changes:
963 new_theme[element] = theme_changes[element]
terryjreedye5bb1122017-07-05 00:54:55 -0400964 # Save the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400965 self.save_new_theme(new_theme_name, new_theme)
terryjreedye5bb1122017-07-05 00:54:55 -0400966 # Change GUI over to the new theme.
terryjreedy938e7382017-06-26 20:48:39 -0400967 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
968 custom_theme_list.sort()
969 self.opt_menu_theme_custom.SetMenu(custom_theme_list, new_theme_name)
970 self.is_builtin_theme.set(0)
971 self.set_theme_type()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000972
terryjreedy7ab33422017-07-09 19:26:32 -0400973 def on_fontlist_select(self, event):
974 """Handle selecting a font from the list.
terryjreedye5bb1122017-07-05 00:54:55 -0400975
terryjreedy7ab33422017-07-09 19:26:32 -0400976 Event can result from either mouse click or Up or Down key.
977 Set font_name and example display to selection.
terryjreedye5bb1122017-07-05 00:54:55 -0400978 """
terryjreedy953e5272017-07-11 02:16:41 -0400979 font = self.fontlist.get(
980 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
terryjreedy938e7382017-06-26 20:48:39 -0400981 self.font_name.set(font.lower())
982 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000983
terryjreedy938e7382017-06-26 20:48:39 -0400984 def set_font_sample(self, event=None):
terryjreedye5bb1122017-07-05 00:54:55 -0400985 "Update the screen samples with the font settings from the dialog."
terryjreedy938e7382017-06-26 20:48:39 -0400986 font_name = self.font_name.get()
987 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
988 new_font = (font_name, self.font_size.get(), font_weight)
989 self.font_sample.config(font=new_font)
990 self.text_highlight_sample.configure(font=new_font)
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000991
terryjreedy938e7382017-06-26 20:48:39 -0400992 def set_highlight_target(self):
terryjreedye5bb1122017-07-05 00:54:55 -0400993 "Set fg/bg toggle and color based on highlight tag target."
994 if self.highlight_target.get() == 'Cursor': # bg not possible
terryjreedy938e7382017-06-26 20:48:39 -0400995 self.radio_fg.config(state=DISABLED)
996 self.radio_bg.config(state=DISABLED)
997 self.fg_bg_toggle.set(1)
terryjreedye5bb1122017-07-05 00:54:55 -0400998 else: # Both fg and bg can be set.
terryjreedy938e7382017-06-26 20:48:39 -0400999 self.radio_fg.config(state=NORMAL)
1000 self.radio_bg.config(state=NORMAL)
1001 self.fg_bg_toggle.set(1)
1002 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001003
terryjreedy938e7382017-06-26 20:48:39 -04001004 def set_colour_sample_binding(self, *args):
terryjreedye5bb1122017-07-05 00:54:55 -04001005 "Change color sample based on foreground/background toggle."
terryjreedy938e7382017-06-26 20:48:39 -04001006 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001007
terryjreedy938e7382017-06-26 20:48:39 -04001008 def set_colour_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001009 "Set the color of the frame background to reflect the selected target."
1010 # Set the colour sample area.
terryjreedy938e7382017-06-26 20:48:39 -04001011 tag = self.theme_elements[self.highlight_target.get()][0]
1012 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
1013 colour = self.text_highlight_sample.tag_cget(tag, plane)
1014 self.frame_colour_set.config(bg=colour)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001015
terryjreedy938e7382017-06-26 20:48:39 -04001016 def paint_theme_sample(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001017 "Apply the theme colors to each element tag in the sample text."
1018 if self.is_builtin_theme.get(): # Default theme
terryjreedy938e7382017-06-26 20:48:39 -04001019 theme = self.builtin_theme.get()
terryjreedye5bb1122017-07-05 00:54:55 -04001020 else: # User theme
terryjreedy938e7382017-06-26 20:48:39 -04001021 theme = self.custom_theme.get()
1022 for element_title in self.theme_elements:
1023 element = self.theme_elements[element_title][0]
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001024 colours = idleConf.GetHighlight(theme, element)
terryjreedye5bb1122017-07-05 00:54:55 -04001025 if element == 'cursor': # Cursor sample needs special painting.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001026 colours['background'] = idleConf.GetHighlight(
1027 theme, 'normal', fgBg='bg')
terryjreedye5bb1122017-07-05 00:54:55 -04001028 # Handle any unsaved changes to this theme.
terryjreedyedc03422017-07-07 16:37:39 -04001029 if theme in changes['highlight']:
1030 theme_dict = changes['highlight'][theme]
terryjreedy938e7382017-06-26 20:48:39 -04001031 if element + '-foreground' in theme_dict:
1032 colours['foreground'] = theme_dict[element + '-foreground']
1033 if element + '-background' in theme_dict:
1034 colours['background'] = theme_dict[element + '-background']
1035 self.text_highlight_sample.tag_config(element, **colours)
1036 self.set_colour_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001037
terryjreedy938e7382017-06-26 20:48:39 -04001038 def help_source_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001039 "Handle event for selecting additional help."
terryjreedy938e7382017-06-26 20:48:39 -04001040 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001041
terryjreedy938e7382017-06-26 20:48:39 -04001042 def set_helplist_button_states(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001043 "Toggle the state for the help list buttons based on list entries."
1044 if self.list_help.size() < 1: # No entries in list.
terryjreedy938e7382017-06-26 20:48:39 -04001045 self.button_helplist_edit.config(state=DISABLED)
1046 self.button_helplist_remove.config(state=DISABLED)
terryjreedye5bb1122017-07-05 00:54:55 -04001047 else: # Some entries.
1048 if self.list_help.curselection(): # There currently is a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001049 self.button_helplist_edit.config(state=NORMAL)
1050 self.button_helplist_remove.config(state=NORMAL)
terryjreedye5bb1122017-07-05 00:54:55 -04001051 else: # There currently is not a selection.
terryjreedy938e7382017-06-26 20:48:39 -04001052 self.button_helplist_edit.config(state=DISABLED)
1053 self.button_helplist_remove.config(state=DISABLED)
Steven M. Gava085eb1b2002-02-05 04:52:32 +00001054
terryjreedy938e7382017-06-26 20:48:39 -04001055 def helplist_item_add(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001056 """Handle add button for the help list.
1057
1058 Query for name and location of new help sources and add
1059 them to the list.
1060 """
terryjreedy938e7382017-06-26 20:48:39 -04001061 help_source = HelpSource(self, 'New Help Source',
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001062 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001063 if help_source:
1064 self.user_helplist.append((help_source[0], help_source[1]))
1065 self.list_help.insert(END, help_source[0])
1066 self.update_user_help_changed_items()
1067 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001068
terryjreedy938e7382017-06-26 20:48:39 -04001069 def helplist_item_edit(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001070 """Handle edit button for the help list.
1071
1072 Query with existing help source information and update
1073 config if the values are changed.
1074 """
terryjreedy938e7382017-06-26 20:48:39 -04001075 item_index = self.list_help.index(ANCHOR)
1076 help_source = self.user_helplist[item_index]
1077 new_help_source = HelpSource(
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001078 self, 'Edit Help Source',
terryjreedy938e7382017-06-26 20:48:39 -04001079 menuitem=help_source[0],
1080 filepath=help_source[1],
Terry Jan Reedy8b22c0a2016-07-08 00:22:50 -04001081 ).result
terryjreedy938e7382017-06-26 20:48:39 -04001082 if new_help_source and new_help_source != help_source:
1083 self.user_helplist[item_index] = new_help_source
1084 self.list_help.delete(item_index)
1085 self.list_help.insert(item_index, new_help_source[0])
1086 self.update_user_help_changed_items()
1087 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001088
terryjreedy938e7382017-06-26 20:48:39 -04001089 def helplist_item_remove(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001090 """Handle remove button for the help list.
1091
1092 Delete the help list item from config.
1093 """
terryjreedy938e7382017-06-26 20:48:39 -04001094 item_index = self.list_help.index(ANCHOR)
1095 del(self.user_helplist[item_index])
1096 self.list_help.delete(item_index)
1097 self.update_user_help_changed_items()
1098 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001099
terryjreedy938e7382017-06-26 20:48:39 -04001100 def update_user_help_changed_items(self):
terryjreedyedc03422017-07-07 16:37:39 -04001101 "Clear and rebuild the HelpFiles section in changes"
1102 changes['main']['HelpFiles'] = {}
terryjreedy938e7382017-06-26 20:48:39 -04001103 for num in range(1, len(self.user_helplist) + 1):
terryjreedyedc03422017-07-07 16:37:39 -04001104 changes.add_option(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001105 'main', 'HelpFiles', str(num),
terryjreedy938e7382017-06-26 20:48:39 -04001106 ';'.join(self.user_helplist[num-1][:2]))
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001107
terryjreedy938e7382017-06-26 20:48:39 -04001108 def load_font_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001109 "Load current configuration settings for the font options."
1110 # Set base editor font selection list.
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001111 fonts = list(tkFont.families(self))
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001112 fonts.sort()
1113 for font in fonts:
terryjreedy7ab33422017-07-09 19:26:32 -04001114 self.fontlist.insert(END, font)
terryjreedy938e7382017-06-26 20:48:39 -04001115 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
1116 font_name = configured_font[0].lower()
1117 font_size = configured_font[1]
1118 font_bold = configured_font[2]=='bold'
1119 self.font_name.set(font_name)
Kurt B. Kaiser05391692003-05-26 20:35:53 +00001120 lc_fonts = [s.lower() for s in fonts]
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001121 try:
terryjreedy938e7382017-06-26 20:48:39 -04001122 current_font_index = lc_fonts.index(font_name)
terryjreedy7ab33422017-07-09 19:26:32 -04001123 self.fontlist.see(current_font_index)
1124 self.fontlist.select_set(current_font_index)
1125 self.fontlist.select_anchor(current_font_index)
1126 self.fontlist.activate(current_font_index)
Terry Jan Reedyd87d1682015-08-01 18:57:33 -04001127 except ValueError:
1128 pass
terryjreedye5bb1122017-07-05 00:54:55 -04001129 # Set font size dropdown.
terryjreedy938e7382017-06-26 20:48:39 -04001130 self.opt_menu_font_size.SetMenu(('7', '8', '9', '10', '11', '12', '13',
Terry Jan Reedyda028872016-08-30 20:19:13 -04001131 '14', '16', '18', '20', '22',
terryjreedy938e7382017-06-26 20:48:39 -04001132 '25', '29', '34', '40'), font_size )
terryjreedye5bb1122017-07-05 00:54:55 -04001133 # Set font weight.
terryjreedy938e7382017-06-26 20:48:39 -04001134 self.font_bold.set(font_bold)
terryjreedye5bb1122017-07-05 00:54:55 -04001135 # Set font sample.
terryjreedy938e7382017-06-26 20:48:39 -04001136 self.set_font_sample()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001137
terryjreedy938e7382017-06-26 20:48:39 -04001138 def load_tab_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001139 "Load current configuration settings for the tab options."
1140 # Set indent sizes.
terryjreedy938e7382017-06-26 20:48:39 -04001141 space_num = idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001142 'main', 'Indent', 'num-spaces', default=4, type='int')
terryjreedy938e7382017-06-26 20:48:39 -04001143 self.space_num.set(space_num)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001144
terryjreedy938e7382017-06-26 20:48:39 -04001145 def load_theme_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001146 "Load current configuration settings for the theme options."
1147 # Set current theme type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001148 self.is_builtin_theme.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001149 'main', 'Theme', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001150 # Set current theme.
terryjreedy938e7382017-06-26 20:48:39 -04001151 current_option = idleConf.CurrentTheme()
terryjreedye5bb1122017-07-05 00:54:55 -04001152 # Load available theme option menus.
1153 if self.is_builtin_theme.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001154 item_list = idleConf.GetSectionList('default', 'highlight')
1155 item_list.sort()
1156 self.opt_menu_theme_builtin.SetMenu(item_list, current_option)
1157 item_list = idleConf.GetSectionList('user', 'highlight')
1158 item_list.sort()
1159 if not item_list:
1160 self.radio_theme_custom.config(state=DISABLED)
1161 self.custom_theme.set('- no custom themes -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001162 else:
terryjreedy938e7382017-06-26 20:48:39 -04001163 self.opt_menu_theme_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001164 else: # User theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001165 item_list = idleConf.GetSectionList('user', 'highlight')
1166 item_list.sort()
1167 self.opt_menu_theme_custom.SetMenu(item_list, current_option)
1168 item_list = idleConf.GetSectionList('default', 'highlight')
1169 item_list.sort()
1170 self.opt_menu_theme_builtin.SetMenu(item_list, item_list[0])
1171 self.set_theme_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001172 # Load theme element option menu.
terryjreedy938e7382017-06-26 20:48:39 -04001173 theme_names = list(self.theme_elements.keys())
1174 theme_names.sort(key=lambda x: self.theme_elements[x][1])
1175 self.opt_menu_highlight_target.SetMenu(theme_names, theme_names[0])
1176 self.paint_theme_sample()
1177 self.set_highlight_target()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001178
terryjreedy938e7382017-06-26 20:48:39 -04001179 def load_key_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001180 "Load current configuration settings for the keybinding options."
1181 # Set current keys type radiobutton.
terryjreedy938e7382017-06-26 20:48:39 -04001182 self.are_keys_builtin.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001183 'main', 'Keys', 'default', type='bool', default=1))
terryjreedye5bb1122017-07-05 00:54:55 -04001184 # Set current keys.
terryjreedy938e7382017-06-26 20:48:39 -04001185 current_option = idleConf.CurrentKeys()
terryjreedye5bb1122017-07-05 00:54:55 -04001186 # Load available keyset option menus.
1187 if self.are_keys_builtin.get(): # Default theme selected.
terryjreedy938e7382017-06-26 20:48:39 -04001188 item_list = idleConf.GetSectionList('default', 'keys')
1189 item_list.sort()
1190 self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
1191 item_list = idleConf.GetSectionList('user', 'keys')
1192 item_list.sort()
1193 if not item_list:
1194 self.radio_keys_custom.config(state=DISABLED)
1195 self.custom_keys.set('- no custom keys -')
Steven M. Gava41a85322001-10-29 08:05:34 +00001196 else:
terryjreedy938e7382017-06-26 20:48:39 -04001197 self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
terryjreedye5bb1122017-07-05 00:54:55 -04001198 else: # User key set selected.
terryjreedy938e7382017-06-26 20:48:39 -04001199 item_list = idleConf.GetSectionList('user', 'keys')
1200 item_list.sort()
1201 self.opt_menu_keys_custom.SetMenu(item_list, current_option)
1202 item_list = idleConf.GetSectionList('default', 'keys')
1203 item_list.sort()
1204 self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
1205 self.set_keys_type()
terryjreedye5bb1122017-07-05 00:54:55 -04001206 # Load keyset element list.
terryjreedy938e7382017-06-26 20:48:39 -04001207 keyset_name = idleConf.CurrentKeys()
1208 self.load_keys_list(keyset_name)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001209
terryjreedy938e7382017-06-26 20:48:39 -04001210 def load_general_cfg(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001211 "Load current configuration settings for the general options."
1212 # Set startup state.
terryjreedy938e7382017-06-26 20:48:39 -04001213 self.startup_edit.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001214 'main', 'General', 'editor-on-startup', default=1, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001215 # Set autosave state.
terryjreedy938e7382017-06-26 20:48:39 -04001216 self.autosave.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001217 'main', 'General', 'autosave', default=0, type='bool'))
terryjreedye5bb1122017-07-05 00:54:55 -04001218 # Set initial window size.
terryjreedy938e7382017-06-26 20:48:39 -04001219 self.win_width.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001220 'main', 'EditorWindow', 'width', type='int'))
terryjreedy938e7382017-06-26 20:48:39 -04001221 self.win_height.set(idleConf.GetOption(
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001222 'main', 'EditorWindow', 'height', type='int'))
terryjreedye5bb1122017-07-05 00:54:55 -04001223 # Set additional help sources.
terryjreedy938e7382017-06-26 20:48:39 -04001224 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
1225 for help_item in self.user_helplist:
1226 self.list_help.insert(END, help_item[0])
1227 self.set_helplist_button_states()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001228
terryjreedy938e7382017-06-26 20:48:39 -04001229 def load_configs(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001230 """Load configuration for each page.
1231
1232 Load configuration from default and user config files and populate
Steven M. Gava429a86af2001-10-23 10:42:12 +00001233 the widgets on the config dialog pages.
1234 """
terryjreedy938e7382017-06-26 20:48:39 -04001235 self.load_font_cfg()
1236 self.load_tab_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001237 self.load_theme_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001238 self.load_key_cfg()
terryjreedy938e7382017-06-26 20:48:39 -04001239 self.load_general_cfg()
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001240 # note: extension page handled separately
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001241
terryjreedy938e7382017-06-26 20:48:39 -04001242 def save_new_key_set(self, keyset_name, keyset):
terryjreedye5bb1122017-07-05 00:54:55 -04001243 """Save a newly created core key set.
1244
terryjreedy938e7382017-06-26 20:48:39 -04001245 keyset_name - string, the name of the new key set
1246 keyset - dictionary containing the new key set
Steven M. Gava052937f2002-02-11 02:20:53 +00001247 """
terryjreedy938e7382017-06-26 20:48:39 -04001248 if not idleConf.userCfg['keys'].has_section(keyset_name):
1249 idleConf.userCfg['keys'].add_section(keyset_name)
1250 for event in keyset:
1251 value = keyset[event]
1252 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001253
terryjreedy938e7382017-06-26 20:48:39 -04001254 def save_new_theme(self, theme_name, theme):
terryjreedye5bb1122017-07-05 00:54:55 -04001255 """Save a newly created theme.
1256
terryjreedy938e7382017-06-26 20:48:39 -04001257 theme_name - string, the name of the new theme
Steven M. Gava052937f2002-02-11 02:20:53 +00001258 theme - dictionary containing the new theme
1259 """
terryjreedy938e7382017-06-26 20:48:39 -04001260 if not idleConf.userCfg['highlight'].has_section(theme_name):
1261 idleConf.userCfg['highlight'].add_section(theme_name)
Kurt B. Kaisere0712772007-08-23 05:25:55 +00001262 for element in theme:
Terry Jan Reedy4036d872014-08-03 23:02:58 -04001263 value = theme[element]
terryjreedy938e7382017-06-26 20:48:39 -04001264 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001265
terryjreedy938e7382017-06-26 20:48:39 -04001266 def deactivate_current_config(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001267 "Remove current key bindings."
1268 # Before a config is saved, some cleanup of current
1269 # config must be done - remove the previous keybindings.
terryjreedy938e7382017-06-26 20:48:39 -04001270 win_instances = self.parent.instance_dict.keys()
1271 for instance in win_instances:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001272 instance.RemoveKeybindings()
1273
terryjreedy938e7382017-06-26 20:48:39 -04001274 def activate_config_changes(self):
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001275 "Dynamically apply configuration changes"
terryjreedy938e7382017-06-26 20:48:39 -04001276 win_instances = self.parent.instance_dict.keys()
1277 for instance in win_instances:
Steven M. Gavab77d3432002-03-02 07:16:21 +00001278 instance.ResetColorizer()
Steven M. Gavab1585412002-03-12 00:21:56 +00001279 instance.ResetFont()
Kurt B. Kaiseracdef852005-01-31 03:34:26 +00001280 instance.set_notabs_indentwidth()
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001281 instance.ApplyKeybindings()
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +00001282 instance.reset_help_menu_entries()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +00001283
terryjreedy938e7382017-06-26 20:48:39 -04001284 def cancel(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001285 "Dismiss config dialog."
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001286 self.destroy()
1287
terryjreedy938e7382017-06-26 20:48:39 -04001288 def ok(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001289 "Apply config changes, then dismiss dialog."
terryjreedy938e7382017-06-26 20:48:39 -04001290 self.apply()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001291 self.destroy()
1292
terryjreedy938e7382017-06-26 20:48:39 -04001293 def apply(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001294 "Apply config changes and leave dialog open."
terryjreedy938e7382017-06-26 20:48:39 -04001295 self.deactivate_current_config()
terryjreedyedc03422017-07-07 16:37:39 -04001296 changes.save_all()
1297 self.save_all_changed_extensions()
terryjreedy938e7382017-06-26 20:48:39 -04001298 self.activate_config_changes()
Steven M. Gava5f28e8f2002-01-21 06:38:21 +00001299
terryjreedy938e7382017-06-26 20:48:39 -04001300 def help(self):
terryjreedye5bb1122017-07-05 00:54:55 -04001301 "Create textview for config dialog help."
terryjreedy938e7382017-06-26 20:48:39 -04001302 page = self.tab_pages._current_page
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001303 view_text(self, title='Help for IDLE preferences',
1304 text=help_common+help_pages.get(page, ''))
1305
terryjreedy938e7382017-06-26 20:48:39 -04001306 def create_page_extensions(self):
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001307 """Part of the config dialog used for configuring IDLE extensions.
1308
1309 This code is generic - it works for any and all IDLE extensions.
1310
1311 IDLE extensions save their configuration options using idleConf.
Terry Jan Reedyb2f87602015-10-13 22:09:06 -04001312 This code reads the current configuration using idleConf, supplies a
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001313 GUI interface to change the configuration values, and saves the
1314 changes using idleConf.
1315
1316 Not all changes take effect immediately - some may require restarting IDLE.
1317 This depends on each extension's implementation.
1318
1319 All values are treated as text, and it is up to the user to supply
1320 reasonable values. The only exception to this are the 'enable*' options,
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +03001321 which are boolean, and can be toggled with a True/False button.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001322 """
1323 parent = self.parent
terryjreedy938e7382017-06-26 20:48:39 -04001324 frame = self.tab_pages.pages['Extensions'].frame
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001325 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1326 self.ext_userCfg = idleConf.userCfg['extensions']
1327 self.is_int = self.register(is_int)
1328 self.load_extensions()
terryjreedye5bb1122017-07-05 00:54:55 -04001329 # Create widgets - a listbox shows all available extensions, with the
1330 # controls for the extension selected in the listbox to the right.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001331 self.extension_names = StringVar(self)
1332 frame.rowconfigure(0, weight=1)
1333 frame.columnconfigure(2, weight=1)
1334 self.extension_list = Listbox(frame, listvariable=self.extension_names,
1335 selectmode='browse')
1336 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1337 scroll = Scrollbar(frame, command=self.extension_list.yview)
1338 self.extension_list.yscrollcommand=scroll.set
1339 self.details_frame = LabelFrame(frame, width=250, height=250)
1340 self.extension_list.grid(column=0, row=0, sticky='nws')
1341 scroll.grid(column=1, row=0, sticky='ns')
1342 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1343 frame.configure(padx=10, pady=10)
1344 self.config_frame = {}
1345 self.current_extension = None
1346
1347 self.outerframe = self # TEMPORARY
1348 self.tabbed_page_set = self.extension_list # TEMPORARY
1349
terryjreedye5bb1122017-07-05 00:54:55 -04001350 # Create the frame holding controls for each extension.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001351 ext_names = ''
1352 for ext_name in sorted(self.extensions):
1353 self.create_extension_frame(ext_name)
1354 ext_names = ext_names + '{' + ext_name + '} '
1355 self.extension_names.set(ext_names)
1356 self.extension_list.selection_set(0)
1357 self.extension_selected(None)
1358
1359 def load_extensions(self):
1360 "Fill self.extensions with data from the default and user configs."
1361 self.extensions = {}
1362 for ext_name in idleConf.GetExtensions(active_only=False):
1363 self.extensions[ext_name] = []
1364
1365 for ext_name in self.extensions:
1366 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1367
terryjreedye5bb1122017-07-05 00:54:55 -04001368 # Bring 'enable' options to the beginning of the list.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001369 enables = [opt_name for opt_name in opt_list
1370 if opt_name.startswith('enable')]
1371 for opt_name in enables:
1372 opt_list.remove(opt_name)
1373 opt_list = enables + opt_list
1374
1375 for opt_name in opt_list:
1376 def_str = self.ext_defaultCfg.Get(
1377 ext_name, opt_name, raw=True)
1378 try:
1379 def_obj = {'True':True, 'False':False}[def_str]
1380 opt_type = 'bool'
1381 except KeyError:
1382 try:
1383 def_obj = int(def_str)
1384 opt_type = 'int'
1385 except ValueError:
1386 def_obj = def_str
1387 opt_type = None
1388 try:
1389 value = self.ext_userCfg.Get(
1390 ext_name, opt_name, type=opt_type, raw=True,
1391 default=def_obj)
terryjreedye5bb1122017-07-05 00:54:55 -04001392 except ValueError: # Need this until .Get fixed.
1393 value = def_obj # Bad values overwritten by entry.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001394 var = StringVar(self)
1395 var.set(str(value))
1396
1397 self.extensions[ext_name].append({'name': opt_name,
1398 'type': opt_type,
1399 'default': def_str,
1400 'value': value,
1401 'var': var,
1402 })
1403
1404 def extension_selected(self, event):
terryjreedye5bb1122017-07-05 00:54:55 -04001405 "Handle selection of an extension from the list."
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001406 newsel = self.extension_list.curselection()
1407 if newsel:
1408 newsel = self.extension_list.get(newsel)
1409 if newsel is None or newsel != self.current_extension:
1410 if self.current_extension:
1411 self.details_frame.config(text='')
1412 self.config_frame[self.current_extension].grid_forget()
1413 self.current_extension = None
1414 if newsel:
1415 self.details_frame.config(text=newsel)
1416 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1417 self.current_extension = newsel
1418
1419 def create_extension_frame(self, ext_name):
1420 """Create a frame holding the widgets to configure one extension"""
1421 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1422 self.config_frame[ext_name] = f
1423 entry_area = f.interior
terryjreedye5bb1122017-07-05 00:54:55 -04001424 # Create an entry for each configuration option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001425 for row, opt in enumerate(self.extensions[ext_name]):
terryjreedye5bb1122017-07-05 00:54:55 -04001426 # Create a row with a label and entry/checkbutton.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001427 label = Label(entry_area, text=opt['name'])
1428 label.grid(row=row, column=0, sticky=NW)
1429 var = opt['var']
1430 if opt['type'] == 'bool':
1431 Checkbutton(entry_area, textvariable=var, variable=var,
1432 onvalue='True', offvalue='False',
1433 indicatoron=FALSE, selectcolor='', width=8
1434 ).grid(row=row, column=1, sticky=W, padx=7)
1435 elif opt['type'] == 'int':
1436 Entry(entry_area, textvariable=var, validate='key',
1437 validatecommand=(self.is_int, '%P')
1438 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1439
1440 else:
1441 Entry(entry_area, textvariable=var
1442 ).grid(row=row, column=1, sticky=NSEW, padx=7)
1443 return
1444
1445 def set_extension_value(self, section, opt):
terryjreedye5bb1122017-07-05 00:54:55 -04001446 """Return True if the configuration was added or changed.
1447
1448 If the value is the same as the default, then remove it
1449 from user config file.
1450 """
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001451 name = opt['name']
1452 default = opt['default']
1453 value = opt['var'].get().strip() or default
1454 opt['var'].set(value)
1455 # if self.defaultCfg.has_section(section):
terryjreedye5bb1122017-07-05 00:54:55 -04001456 # Currently, always true; if not, indent to return.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001457 if (value == default):
1458 return self.ext_userCfg.RemoveOption(section, name)
terryjreedye5bb1122017-07-05 00:54:55 -04001459 # Set the option.
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001460 return self.ext_userCfg.SetOption(section, name, value)
1461
1462 def save_all_changed_extensions(self):
1463 """Save configuration changes to the user config file."""
1464 has_changes = False
1465 for ext_name in self.extensions:
1466 options = self.extensions[ext_name]
1467 for opt in options:
1468 if self.set_extension_value(ext_name, opt):
1469 has_changes = True
1470 if has_changes:
1471 self.ext_userCfg.Save()
1472
1473
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001474help_common = '''\
1475When you click either the Apply or Ok buttons, settings in this
1476dialog that are different from IDLE's default are saved in
1477a .idlerc directory in your home directory. Except as noted,
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001478these changes apply to all versions of IDLE installed on this
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001479machine. Some do not take affect until IDLE is restarted.
1480[Cancel] only cancels changes made since the last save.
1481'''
1482help_pages = {
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001483 'Highlighting': '''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001484Highlighting:
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -05001485The IDLE Dark color theme is new in October 2015. It can only
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001486be used with older IDLE releases if it is saved as a custom
1487theme, with a different name.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -04001488''',
1489 'Keys': '''
1490Keys:
1491The IDLE Modern Unix key set is new in June 2016. It can only
1492be used with older IDLE releases if it is saved as a custom
1493key set, with a different name.
1494''',
terryjreedyaf683822017-06-27 23:02:19 -04001495 'Extensions': '''
1496Extensions:
1497
1498Autocomplete: Popupwait is milleseconds to wait after key char, without
1499cursor movement, before popping up completion box. Key char is '.' after
1500identifier or a '/' (or '\\' on Windows) within a string.
1501
1502FormatParagraph: Max-width is max chars in lines after re-formatting.
1503Use with paragraphs in both strings and comment blocks.
1504
1505ParenMatch: Style indicates what is highlighted when closer is entered:
1506'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
1507'expression' (default) - also everything in between. Flash-delay is how
1508long to highlight if cursor is not moved (0 means forever).
1509'''
Terry Jan Reedyd0cadba2015-10-11 22:07:31 -04001510}
1511
Steven M. Gavac11ccf32001-09-24 09:43:17 +00001512
Terry Jan Reedy93f35422015-10-13 22:03:51 -04001513def is_int(s):
1514 "Return 's is blank or represents an int'"
1515 if not s:
1516 return True
1517 try:
1518 int(s)
1519 return True
1520 except ValueError:
1521 return False
1522
1523
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001524class VerticalScrolledFrame(Frame):
1525 """A pure Tkinter vertically scrollable frame.
1526
1527 * Use the 'interior' attribute to place widgets inside the scrollable frame
1528 * Construct and pack/place/grid normally
1529 * This frame only allows vertical scrolling
1530 """
1531 def __init__(self, parent, *args, **kw):
1532 Frame.__init__(self, parent, *args, **kw)
1533
terryjreedye5bb1122017-07-05 00:54:55 -04001534 # Create a canvas object and a vertical scrollbar for scrolling it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001535 vscrollbar = Scrollbar(self, orient=VERTICAL)
1536 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1537 canvas = Canvas(self, bd=0, highlightthickness=0,
Terry Jan Reedyd0812292015-10-22 03:27:31 -04001538 yscrollcommand=vscrollbar.set, width=240)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001539 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1540 vscrollbar.config(command=canvas.yview)
1541
terryjreedye5bb1122017-07-05 00:54:55 -04001542 # Reset the view.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001543 canvas.xview_moveto(0)
1544 canvas.yview_moveto(0)
1545
terryjreedye5bb1122017-07-05 00:54:55 -04001546 # Create a frame inside the canvas which will be scrolled with it.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001547 self.interior = interior = Frame(canvas)
1548 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1549
terryjreedye5bb1122017-07-05 00:54:55 -04001550 # Track changes to the canvas and frame width and sync them,
1551 # also updating the scrollbar.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001552 def _configure_interior(event):
terryjreedye5bb1122017-07-05 00:54:55 -04001553 # Update the scrollbars to match the size of the inner frame.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001554 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1555 canvas.config(scrollregion="0 0 %s %s" % size)
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001556 interior.bind('<Configure>', _configure_interior)
1557
1558 def _configure_canvas(event):
1559 if interior.winfo_reqwidth() != canvas.winfo_width():
terryjreedye5bb1122017-07-05 00:54:55 -04001560 # Update the inner frame's width to fill the canvas.
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001561 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1562 canvas.bind('<Configure>', _configure_canvas)
1563
1564 return
1565
Terry Jan Reedya9421fb2014-10-22 20:15:18 -04001566
Steven M. Gava44d3d1a2001-07-31 06:59:02 +00001567if __name__ == '__main__':
Terry Jan Reedycfa89502014-07-14 23:07:32 -04001568 import unittest
1569 unittest.main('idlelib.idle_test.test_configdialog',
1570 verbosity=2, exit=False)
Terry Jan Reedy2e8234a2014-05-29 01:46:26 -04001571 from idlelib.idle_test.htest import run
Terry Jan Reedy47304c02015-10-20 02:15:28 -04001572 run(ConfigDialog)