jpayne@68
|
1 """IDLE Configuration Dialog: support user customization of IDLE by GUI
|
jpayne@68
|
2
|
jpayne@68
|
3 Customize font faces, sizes, and colorization attributes. Set indentation
|
jpayne@68
|
4 defaults. Customize keybindings. Colorization and keybindings can be
|
jpayne@68
|
5 saved as user defined sets. Select startup options including shell/editor
|
jpayne@68
|
6 and default window size. Define additional help sources.
|
jpayne@68
|
7
|
jpayne@68
|
8 Note that tab width in IDLE is currently fixed at eight due to Tk issues.
|
jpayne@68
|
9 Refer to comments in EditorWindow autoindent code for details.
|
jpayne@68
|
10
|
jpayne@68
|
11 """
|
jpayne@68
|
12 import re
|
jpayne@68
|
13
|
jpayne@68
|
14 from tkinter import (Toplevel, Listbox, Text, Scale, Canvas,
|
jpayne@68
|
15 StringVar, BooleanVar, IntVar, TRUE, FALSE,
|
jpayne@68
|
16 TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE,
|
jpayne@68
|
17 NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
|
jpayne@68
|
18 HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
|
jpayne@68
|
19 from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label,
|
jpayne@68
|
20 OptionMenu, Notebook, Radiobutton, Scrollbar, Style)
|
jpayne@68
|
21 import tkinter.colorchooser as tkColorChooser
|
jpayne@68
|
22 import tkinter.font as tkFont
|
jpayne@68
|
23 from tkinter import messagebox
|
jpayne@68
|
24
|
jpayne@68
|
25 from idlelib.config import idleConf, ConfigChanges
|
jpayne@68
|
26 from idlelib.config_key import GetKeysDialog
|
jpayne@68
|
27 from idlelib.dynoption import DynOptionMenu
|
jpayne@68
|
28 from idlelib import macosx
|
jpayne@68
|
29 from idlelib.query import SectionName, HelpSource
|
jpayne@68
|
30 from idlelib.textview import view_text
|
jpayne@68
|
31 from idlelib.autocomplete import AutoComplete
|
jpayne@68
|
32 from idlelib.codecontext import CodeContext
|
jpayne@68
|
33 from idlelib.parenmatch import ParenMatch
|
jpayne@68
|
34 from idlelib.format import FormatParagraph
|
jpayne@68
|
35 from idlelib.squeezer import Squeezer
|
jpayne@68
|
36 from idlelib.textview import ScrollableTextFrame
|
jpayne@68
|
37
|
jpayne@68
|
38 changes = ConfigChanges()
|
jpayne@68
|
39 # Reload changed options in the following classes.
|
jpayne@68
|
40 reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
|
jpayne@68
|
41 Squeezer)
|
jpayne@68
|
42
|
jpayne@68
|
43
|
jpayne@68
|
44 class ConfigDialog(Toplevel):
|
jpayne@68
|
45 """Config dialog for IDLE.
|
jpayne@68
|
46 """
|
jpayne@68
|
47
|
jpayne@68
|
48 def __init__(self, parent, title='', *, _htest=False, _utest=False):
|
jpayne@68
|
49 """Show the tabbed dialog for user configuration.
|
jpayne@68
|
50
|
jpayne@68
|
51 Args:
|
jpayne@68
|
52 parent - parent of this dialog
|
jpayne@68
|
53 title - string which is the title of this popup dialog
|
jpayne@68
|
54 _htest - bool, change box location when running htest
|
jpayne@68
|
55 _utest - bool, don't wait_window when running unittest
|
jpayne@68
|
56
|
jpayne@68
|
57 Note: Focus set on font page fontlist.
|
jpayne@68
|
58
|
jpayne@68
|
59 Methods:
|
jpayne@68
|
60 create_widgets
|
jpayne@68
|
61 cancel: Bound to DELETE_WINDOW protocol.
|
jpayne@68
|
62 """
|
jpayne@68
|
63 Toplevel.__init__(self, parent)
|
jpayne@68
|
64 self.parent = parent
|
jpayne@68
|
65 if _htest:
|
jpayne@68
|
66 parent.instance_dict = {}
|
jpayne@68
|
67 if not _utest:
|
jpayne@68
|
68 self.withdraw()
|
jpayne@68
|
69
|
jpayne@68
|
70 self.configure(borderwidth=5)
|
jpayne@68
|
71 self.title(title or 'IDLE Preferences')
|
jpayne@68
|
72 x = parent.winfo_rootx() + 20
|
jpayne@68
|
73 y = parent.winfo_rooty() + (30 if not _htest else 150)
|
jpayne@68
|
74 self.geometry(f'+{x}+{y}')
|
jpayne@68
|
75 # Each theme element key is its display name.
|
jpayne@68
|
76 # The first value of the tuple is the sample area tag name.
|
jpayne@68
|
77 # The second value is the display name list sort index.
|
jpayne@68
|
78 self.create_widgets()
|
jpayne@68
|
79 self.resizable(height=FALSE, width=FALSE)
|
jpayne@68
|
80 self.transient(parent)
|
jpayne@68
|
81 self.protocol("WM_DELETE_WINDOW", self.cancel)
|
jpayne@68
|
82 self.fontpage.fontlist.focus_set()
|
jpayne@68
|
83 # XXX Decide whether to keep or delete these key bindings.
|
jpayne@68
|
84 # Key bindings for this dialog.
|
jpayne@68
|
85 # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
|
jpayne@68
|
86 # self.bind('<Alt-a>', self.Apply) #apply changes, save
|
jpayne@68
|
87 # self.bind('<F1>', self.Help) #context help
|
jpayne@68
|
88 # Attach callbacks after loading config to avoid calling them.
|
jpayne@68
|
89 tracers.attach()
|
jpayne@68
|
90
|
jpayne@68
|
91 if not _utest:
|
jpayne@68
|
92 self.grab_set()
|
jpayne@68
|
93 self.wm_deiconify()
|
jpayne@68
|
94 self.wait_window()
|
jpayne@68
|
95
|
jpayne@68
|
96 def create_widgets(self):
|
jpayne@68
|
97 """Create and place widgets for tabbed dialog.
|
jpayne@68
|
98
|
jpayne@68
|
99 Widgets Bound to self:
|
jpayne@68
|
100 note: Notebook
|
jpayne@68
|
101 highpage: HighPage
|
jpayne@68
|
102 fontpage: FontPage
|
jpayne@68
|
103 keyspage: KeysPage
|
jpayne@68
|
104 genpage: GenPage
|
jpayne@68
|
105 extpage: self.create_page_extensions
|
jpayne@68
|
106
|
jpayne@68
|
107 Methods:
|
jpayne@68
|
108 create_action_buttons
|
jpayne@68
|
109 load_configs: Load pages except for extensions.
|
jpayne@68
|
110 activate_config_changes: Tell editors to reload.
|
jpayne@68
|
111 """
|
jpayne@68
|
112 self.note = note = Notebook(self)
|
jpayne@68
|
113 self.highpage = HighPage(note)
|
jpayne@68
|
114 self.fontpage = FontPage(note, self.highpage)
|
jpayne@68
|
115 self.keyspage = KeysPage(note)
|
jpayne@68
|
116 self.genpage = GenPage(note)
|
jpayne@68
|
117 self.extpage = self.create_page_extensions()
|
jpayne@68
|
118 note.add(self.fontpage, text='Fonts/Tabs')
|
jpayne@68
|
119 note.add(self.highpage, text='Highlights')
|
jpayne@68
|
120 note.add(self.keyspage, text=' Keys ')
|
jpayne@68
|
121 note.add(self.genpage, text=' General ')
|
jpayne@68
|
122 note.add(self.extpage, text='Extensions')
|
jpayne@68
|
123 note.enable_traversal()
|
jpayne@68
|
124 note.pack(side=TOP, expand=TRUE, fill=BOTH)
|
jpayne@68
|
125 self.create_action_buttons().pack(side=BOTTOM)
|
jpayne@68
|
126
|
jpayne@68
|
127 def create_action_buttons(self):
|
jpayne@68
|
128 """Return frame of action buttons for dialog.
|
jpayne@68
|
129
|
jpayne@68
|
130 Methods:
|
jpayne@68
|
131 ok
|
jpayne@68
|
132 apply
|
jpayne@68
|
133 cancel
|
jpayne@68
|
134 help
|
jpayne@68
|
135
|
jpayne@68
|
136 Widget Structure:
|
jpayne@68
|
137 outer: Frame
|
jpayne@68
|
138 buttons: Frame
|
jpayne@68
|
139 (no assignment): Button (ok)
|
jpayne@68
|
140 (no assignment): Button (apply)
|
jpayne@68
|
141 (no assignment): Button (cancel)
|
jpayne@68
|
142 (no assignment): Button (help)
|
jpayne@68
|
143 (no assignment): Frame
|
jpayne@68
|
144 """
|
jpayne@68
|
145 if macosx.isAquaTk():
|
jpayne@68
|
146 # Changing the default padding on OSX results in unreadable
|
jpayne@68
|
147 # text in the buttons.
|
jpayne@68
|
148 padding_args = {}
|
jpayne@68
|
149 else:
|
jpayne@68
|
150 padding_args = {'padding': (6, 3)}
|
jpayne@68
|
151 outer = Frame(self, padding=2)
|
jpayne@68
|
152 buttons = Frame(outer, padding=2)
|
jpayne@68
|
153 for txt, cmd in (
|
jpayne@68
|
154 ('Ok', self.ok),
|
jpayne@68
|
155 ('Apply', self.apply),
|
jpayne@68
|
156 ('Cancel', self.cancel),
|
jpayne@68
|
157 ('Help', self.help)):
|
jpayne@68
|
158 Button(buttons, text=txt, command=cmd, takefocus=FALSE,
|
jpayne@68
|
159 **padding_args).pack(side=LEFT, padx=5)
|
jpayne@68
|
160 # Add space above buttons.
|
jpayne@68
|
161 Frame(outer, height=2, borderwidth=0).pack(side=TOP)
|
jpayne@68
|
162 buttons.pack(side=BOTTOM)
|
jpayne@68
|
163 return outer
|
jpayne@68
|
164
|
jpayne@68
|
165 def ok(self):
|
jpayne@68
|
166 """Apply config changes, then dismiss dialog.
|
jpayne@68
|
167
|
jpayne@68
|
168 Methods:
|
jpayne@68
|
169 apply
|
jpayne@68
|
170 destroy: inherited
|
jpayne@68
|
171 """
|
jpayne@68
|
172 self.apply()
|
jpayne@68
|
173 self.destroy()
|
jpayne@68
|
174
|
jpayne@68
|
175 def apply(self):
|
jpayne@68
|
176 """Apply config changes and leave dialog open.
|
jpayne@68
|
177
|
jpayne@68
|
178 Methods:
|
jpayne@68
|
179 deactivate_current_config
|
jpayne@68
|
180 save_all_changed_extensions
|
jpayne@68
|
181 activate_config_changes
|
jpayne@68
|
182 """
|
jpayne@68
|
183 self.deactivate_current_config()
|
jpayne@68
|
184 changes.save_all()
|
jpayne@68
|
185 self.save_all_changed_extensions()
|
jpayne@68
|
186 self.activate_config_changes()
|
jpayne@68
|
187
|
jpayne@68
|
188 def cancel(self):
|
jpayne@68
|
189 """Dismiss config dialog.
|
jpayne@68
|
190
|
jpayne@68
|
191 Methods:
|
jpayne@68
|
192 destroy: inherited
|
jpayne@68
|
193 """
|
jpayne@68
|
194 self.destroy()
|
jpayne@68
|
195
|
jpayne@68
|
196 def destroy(self):
|
jpayne@68
|
197 global font_sample_text
|
jpayne@68
|
198 font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
|
jpayne@68
|
199 self.grab_release()
|
jpayne@68
|
200 super().destroy()
|
jpayne@68
|
201
|
jpayne@68
|
202 def help(self):
|
jpayne@68
|
203 """Create textview for config dialog help.
|
jpayne@68
|
204
|
jpayne@68
|
205 Attributes accessed:
|
jpayne@68
|
206 note
|
jpayne@68
|
207
|
jpayne@68
|
208 Methods:
|
jpayne@68
|
209 view_text: Method from textview module.
|
jpayne@68
|
210 """
|
jpayne@68
|
211 page = self.note.tab(self.note.select(), option='text').strip()
|
jpayne@68
|
212 view_text(self, title='Help for IDLE preferences',
|
jpayne@68
|
213 text=help_common+help_pages.get(page, ''))
|
jpayne@68
|
214
|
jpayne@68
|
215 def deactivate_current_config(self):
|
jpayne@68
|
216 """Remove current key bindings.
|
jpayne@68
|
217 Iterate over window instances defined in parent and remove
|
jpayne@68
|
218 the keybindings.
|
jpayne@68
|
219 """
|
jpayne@68
|
220 # Before a config is saved, some cleanup of current
|
jpayne@68
|
221 # config must be done - remove the previous keybindings.
|
jpayne@68
|
222 win_instances = self.parent.instance_dict.keys()
|
jpayne@68
|
223 for instance in win_instances:
|
jpayne@68
|
224 instance.RemoveKeybindings()
|
jpayne@68
|
225
|
jpayne@68
|
226 def activate_config_changes(self):
|
jpayne@68
|
227 """Apply configuration changes to current windows.
|
jpayne@68
|
228
|
jpayne@68
|
229 Dynamically update the current parent window instances
|
jpayne@68
|
230 with some of the configuration changes.
|
jpayne@68
|
231 """
|
jpayne@68
|
232 win_instances = self.parent.instance_dict.keys()
|
jpayne@68
|
233 for instance in win_instances:
|
jpayne@68
|
234 instance.ResetColorizer()
|
jpayne@68
|
235 instance.ResetFont()
|
jpayne@68
|
236 instance.set_notabs_indentwidth()
|
jpayne@68
|
237 instance.ApplyKeybindings()
|
jpayne@68
|
238 instance.reset_help_menu_entries()
|
jpayne@68
|
239 instance.update_cursor_blink()
|
jpayne@68
|
240 for klass in reloadables:
|
jpayne@68
|
241 klass.reload()
|
jpayne@68
|
242
|
jpayne@68
|
243 def create_page_extensions(self):
|
jpayne@68
|
244 """Part of the config dialog used for configuring IDLE extensions.
|
jpayne@68
|
245
|
jpayne@68
|
246 This code is generic - it works for any and all IDLE extensions.
|
jpayne@68
|
247
|
jpayne@68
|
248 IDLE extensions save their configuration options using idleConf.
|
jpayne@68
|
249 This code reads the current configuration using idleConf, supplies a
|
jpayne@68
|
250 GUI interface to change the configuration values, and saves the
|
jpayne@68
|
251 changes using idleConf.
|
jpayne@68
|
252
|
jpayne@68
|
253 Not all changes take effect immediately - some may require restarting IDLE.
|
jpayne@68
|
254 This depends on each extension's implementation.
|
jpayne@68
|
255
|
jpayne@68
|
256 All values are treated as text, and it is up to the user to supply
|
jpayne@68
|
257 reasonable values. The only exception to this are the 'enable*' options,
|
jpayne@68
|
258 which are boolean, and can be toggled with a True/False button.
|
jpayne@68
|
259
|
jpayne@68
|
260 Methods:
|
jpayne@68
|
261 load_extensions:
|
jpayne@68
|
262 extension_selected: Handle selection from list.
|
jpayne@68
|
263 create_extension_frame: Hold widgets for one extension.
|
jpayne@68
|
264 set_extension_value: Set in userCfg['extensions'].
|
jpayne@68
|
265 save_all_changed_extensions: Call extension page Save().
|
jpayne@68
|
266 """
|
jpayne@68
|
267 parent = self.parent
|
jpayne@68
|
268 frame = Frame(self.note)
|
jpayne@68
|
269 self.ext_defaultCfg = idleConf.defaultCfg['extensions']
|
jpayne@68
|
270 self.ext_userCfg = idleConf.userCfg['extensions']
|
jpayne@68
|
271 self.is_int = self.register(is_int)
|
jpayne@68
|
272 self.load_extensions()
|
jpayne@68
|
273 # Create widgets - a listbox shows all available extensions, with the
|
jpayne@68
|
274 # controls for the extension selected in the listbox to the right.
|
jpayne@68
|
275 self.extension_names = StringVar(self)
|
jpayne@68
|
276 frame.rowconfigure(0, weight=1)
|
jpayne@68
|
277 frame.columnconfigure(2, weight=1)
|
jpayne@68
|
278 self.extension_list = Listbox(frame, listvariable=self.extension_names,
|
jpayne@68
|
279 selectmode='browse')
|
jpayne@68
|
280 self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
|
jpayne@68
|
281 scroll = Scrollbar(frame, command=self.extension_list.yview)
|
jpayne@68
|
282 self.extension_list.yscrollcommand=scroll.set
|
jpayne@68
|
283 self.details_frame = LabelFrame(frame, width=250, height=250)
|
jpayne@68
|
284 self.extension_list.grid(column=0, row=0, sticky='nws')
|
jpayne@68
|
285 scroll.grid(column=1, row=0, sticky='ns')
|
jpayne@68
|
286 self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
|
jpayne@68
|
287 frame.configure(padding=10)
|
jpayne@68
|
288 self.config_frame = {}
|
jpayne@68
|
289 self.current_extension = None
|
jpayne@68
|
290
|
jpayne@68
|
291 self.outerframe = self # TEMPORARY
|
jpayne@68
|
292 self.tabbed_page_set = self.extension_list # TEMPORARY
|
jpayne@68
|
293
|
jpayne@68
|
294 # Create the frame holding controls for each extension.
|
jpayne@68
|
295 ext_names = ''
|
jpayne@68
|
296 for ext_name in sorted(self.extensions):
|
jpayne@68
|
297 self.create_extension_frame(ext_name)
|
jpayne@68
|
298 ext_names = ext_names + '{' + ext_name + '} '
|
jpayne@68
|
299 self.extension_names.set(ext_names)
|
jpayne@68
|
300 self.extension_list.selection_set(0)
|
jpayne@68
|
301 self.extension_selected(None)
|
jpayne@68
|
302
|
jpayne@68
|
303 return frame
|
jpayne@68
|
304
|
jpayne@68
|
305 def load_extensions(self):
|
jpayne@68
|
306 "Fill self.extensions with data from the default and user configs."
|
jpayne@68
|
307 self.extensions = {}
|
jpayne@68
|
308 for ext_name in idleConf.GetExtensions(active_only=False):
|
jpayne@68
|
309 # Former built-in extensions are already filtered out.
|
jpayne@68
|
310 self.extensions[ext_name] = []
|
jpayne@68
|
311
|
jpayne@68
|
312 for ext_name in self.extensions:
|
jpayne@68
|
313 opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
|
jpayne@68
|
314
|
jpayne@68
|
315 # Bring 'enable' options to the beginning of the list.
|
jpayne@68
|
316 enables = [opt_name for opt_name in opt_list
|
jpayne@68
|
317 if opt_name.startswith('enable')]
|
jpayne@68
|
318 for opt_name in enables:
|
jpayne@68
|
319 opt_list.remove(opt_name)
|
jpayne@68
|
320 opt_list = enables + opt_list
|
jpayne@68
|
321
|
jpayne@68
|
322 for opt_name in opt_list:
|
jpayne@68
|
323 def_str = self.ext_defaultCfg.Get(
|
jpayne@68
|
324 ext_name, opt_name, raw=True)
|
jpayne@68
|
325 try:
|
jpayne@68
|
326 def_obj = {'True':True, 'False':False}[def_str]
|
jpayne@68
|
327 opt_type = 'bool'
|
jpayne@68
|
328 except KeyError:
|
jpayne@68
|
329 try:
|
jpayne@68
|
330 def_obj = int(def_str)
|
jpayne@68
|
331 opt_type = 'int'
|
jpayne@68
|
332 except ValueError:
|
jpayne@68
|
333 def_obj = def_str
|
jpayne@68
|
334 opt_type = None
|
jpayne@68
|
335 try:
|
jpayne@68
|
336 value = self.ext_userCfg.Get(
|
jpayne@68
|
337 ext_name, opt_name, type=opt_type, raw=True,
|
jpayne@68
|
338 default=def_obj)
|
jpayne@68
|
339 except ValueError: # Need this until .Get fixed.
|
jpayne@68
|
340 value = def_obj # Bad values overwritten by entry.
|
jpayne@68
|
341 var = StringVar(self)
|
jpayne@68
|
342 var.set(str(value))
|
jpayne@68
|
343
|
jpayne@68
|
344 self.extensions[ext_name].append({'name': opt_name,
|
jpayne@68
|
345 'type': opt_type,
|
jpayne@68
|
346 'default': def_str,
|
jpayne@68
|
347 'value': value,
|
jpayne@68
|
348 'var': var,
|
jpayne@68
|
349 })
|
jpayne@68
|
350
|
jpayne@68
|
351 def extension_selected(self, event):
|
jpayne@68
|
352 "Handle selection of an extension from the list."
|
jpayne@68
|
353 newsel = self.extension_list.curselection()
|
jpayne@68
|
354 if newsel:
|
jpayne@68
|
355 newsel = self.extension_list.get(newsel)
|
jpayne@68
|
356 if newsel is None or newsel != self.current_extension:
|
jpayne@68
|
357 if self.current_extension:
|
jpayne@68
|
358 self.details_frame.config(text='')
|
jpayne@68
|
359 self.config_frame[self.current_extension].grid_forget()
|
jpayne@68
|
360 self.current_extension = None
|
jpayne@68
|
361 if newsel:
|
jpayne@68
|
362 self.details_frame.config(text=newsel)
|
jpayne@68
|
363 self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
|
jpayne@68
|
364 self.current_extension = newsel
|
jpayne@68
|
365
|
jpayne@68
|
366 def create_extension_frame(self, ext_name):
|
jpayne@68
|
367 """Create a frame holding the widgets to configure one extension"""
|
jpayne@68
|
368 f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
|
jpayne@68
|
369 self.config_frame[ext_name] = f
|
jpayne@68
|
370 entry_area = f.interior
|
jpayne@68
|
371 # Create an entry for each configuration option.
|
jpayne@68
|
372 for row, opt in enumerate(self.extensions[ext_name]):
|
jpayne@68
|
373 # Create a row with a label and entry/checkbutton.
|
jpayne@68
|
374 label = Label(entry_area, text=opt['name'])
|
jpayne@68
|
375 label.grid(row=row, column=0, sticky=NW)
|
jpayne@68
|
376 var = opt['var']
|
jpayne@68
|
377 if opt['type'] == 'bool':
|
jpayne@68
|
378 Checkbutton(entry_area, variable=var,
|
jpayne@68
|
379 onvalue='True', offvalue='False', width=8
|
jpayne@68
|
380 ).grid(row=row, column=1, sticky=W, padx=7)
|
jpayne@68
|
381 elif opt['type'] == 'int':
|
jpayne@68
|
382 Entry(entry_area, textvariable=var, validate='key',
|
jpayne@68
|
383 validatecommand=(self.is_int, '%P'), width=10
|
jpayne@68
|
384 ).grid(row=row, column=1, sticky=NSEW, padx=7)
|
jpayne@68
|
385
|
jpayne@68
|
386 else: # type == 'str'
|
jpayne@68
|
387 # Limit size to fit non-expanding space with larger font.
|
jpayne@68
|
388 Entry(entry_area, textvariable=var, width=15
|
jpayne@68
|
389 ).grid(row=row, column=1, sticky=NSEW, padx=7)
|
jpayne@68
|
390 return
|
jpayne@68
|
391
|
jpayne@68
|
392 def set_extension_value(self, section, opt):
|
jpayne@68
|
393 """Return True if the configuration was added or changed.
|
jpayne@68
|
394
|
jpayne@68
|
395 If the value is the same as the default, then remove it
|
jpayne@68
|
396 from user config file.
|
jpayne@68
|
397 """
|
jpayne@68
|
398 name = opt['name']
|
jpayne@68
|
399 default = opt['default']
|
jpayne@68
|
400 value = opt['var'].get().strip() or default
|
jpayne@68
|
401 opt['var'].set(value)
|
jpayne@68
|
402 # if self.defaultCfg.has_section(section):
|
jpayne@68
|
403 # Currently, always true; if not, indent to return.
|
jpayne@68
|
404 if (value == default):
|
jpayne@68
|
405 return self.ext_userCfg.RemoveOption(section, name)
|
jpayne@68
|
406 # Set the option.
|
jpayne@68
|
407 return self.ext_userCfg.SetOption(section, name, value)
|
jpayne@68
|
408
|
jpayne@68
|
409 def save_all_changed_extensions(self):
|
jpayne@68
|
410 """Save configuration changes to the user config file.
|
jpayne@68
|
411
|
jpayne@68
|
412 Attributes accessed:
|
jpayne@68
|
413 extensions
|
jpayne@68
|
414
|
jpayne@68
|
415 Methods:
|
jpayne@68
|
416 set_extension_value
|
jpayne@68
|
417 """
|
jpayne@68
|
418 has_changes = False
|
jpayne@68
|
419 for ext_name in self.extensions:
|
jpayne@68
|
420 options = self.extensions[ext_name]
|
jpayne@68
|
421 for opt in options:
|
jpayne@68
|
422 if self.set_extension_value(ext_name, opt):
|
jpayne@68
|
423 has_changes = True
|
jpayne@68
|
424 if has_changes:
|
jpayne@68
|
425 self.ext_userCfg.Save()
|
jpayne@68
|
426
|
jpayne@68
|
427
|
jpayne@68
|
428 # class TabPage(Frame): # A template for Page classes.
|
jpayne@68
|
429 # def __init__(self, master):
|
jpayne@68
|
430 # super().__init__(master)
|
jpayne@68
|
431 # self.create_page_tab()
|
jpayne@68
|
432 # self.load_tab_cfg()
|
jpayne@68
|
433 # def create_page_tab(self):
|
jpayne@68
|
434 # # Define tk vars and register var and callback with tracers.
|
jpayne@68
|
435 # # Create subframes and widgets.
|
jpayne@68
|
436 # # Pack widgets.
|
jpayne@68
|
437 # def load_tab_cfg(self):
|
jpayne@68
|
438 # # Initialize widgets with data from idleConf.
|
jpayne@68
|
439 # def var_changed_var_name():
|
jpayne@68
|
440 # # For each tk var that needs other than default callback.
|
jpayne@68
|
441 # def other_methods():
|
jpayne@68
|
442 # # Define tab-specific behavior.
|
jpayne@68
|
443
|
jpayne@68
|
444 font_sample_text = (
|
jpayne@68
|
445 '<ASCII/Latin1>\n'
|
jpayne@68
|
446 'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
|
jpayne@68
|
447 '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
|
jpayne@68
|
448 '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
|
jpayne@68
|
449 '\n<IPA,Greek,Cyrillic>\n'
|
jpayne@68
|
450 '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
|
jpayne@68
|
451 '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
|
jpayne@68
|
452 '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
|
jpayne@68
|
453 '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
|
jpayne@68
|
454 '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
|
jpayne@68
|
455 '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
|
jpayne@68
|
456 '\n<Hebrew, Arabic>\n'
|
jpayne@68
|
457 '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
|
jpayne@68
|
458 '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
|
jpayne@68
|
459 '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
|
jpayne@68
|
460 '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
|
jpayne@68
|
461 '\n<Devanagari, Tamil>\n'
|
jpayne@68
|
462 '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
|
jpayne@68
|
463 '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
|
jpayne@68
|
464 '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
|
jpayne@68
|
465 '\u0b85\u0b87\u0b89\u0b8e\n'
|
jpayne@68
|
466 '\n<East Asian>\n'
|
jpayne@68
|
467 '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
|
jpayne@68
|
468 '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
|
jpayne@68
|
469 '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
|
jpayne@68
|
470 '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
|
jpayne@68
|
471 )
|
jpayne@68
|
472
|
jpayne@68
|
473
|
jpayne@68
|
474 class FontPage(Frame):
|
jpayne@68
|
475
|
jpayne@68
|
476 def __init__(self, master, highpage):
|
jpayne@68
|
477 super().__init__(master)
|
jpayne@68
|
478 self.highlight_sample = highpage.highlight_sample
|
jpayne@68
|
479 self.create_page_font_tab()
|
jpayne@68
|
480 self.load_font_cfg()
|
jpayne@68
|
481 self.load_tab_cfg()
|
jpayne@68
|
482
|
jpayne@68
|
483 def create_page_font_tab(self):
|
jpayne@68
|
484 """Return frame of widgets for Font/Tabs tab.
|
jpayne@68
|
485
|
jpayne@68
|
486 Fonts: Enable users to provisionally change font face, size, or
|
jpayne@68
|
487 boldness and to see the consequence of proposed choices. Each
|
jpayne@68
|
488 action set 3 options in changes structuree and changes the
|
jpayne@68
|
489 corresponding aspect of the font sample on this page and
|
jpayne@68
|
490 highlight sample on highlight page.
|
jpayne@68
|
491
|
jpayne@68
|
492 Function load_font_cfg initializes font vars and widgets from
|
jpayne@68
|
493 idleConf entries and tk.
|
jpayne@68
|
494
|
jpayne@68
|
495 Fontlist: mouse button 1 click or up or down key invoke
|
jpayne@68
|
496 on_fontlist_select(), which sets var font_name.
|
jpayne@68
|
497
|
jpayne@68
|
498 Sizelist: clicking the menubutton opens the dropdown menu. A
|
jpayne@68
|
499 mouse button 1 click or return key sets var font_size.
|
jpayne@68
|
500
|
jpayne@68
|
501 Bold_toggle: clicking the box toggles var font_bold.
|
jpayne@68
|
502
|
jpayne@68
|
503 Changing any of the font vars invokes var_changed_font, which
|
jpayne@68
|
504 adds all 3 font options to changes and calls set_samples.
|
jpayne@68
|
505 Set_samples applies a new font constructed from the font vars to
|
jpayne@68
|
506 font_sample and to highlight_sample on the highlight page.
|
jpayne@68
|
507
|
jpayne@68
|
508 Tabs: Enable users to change spaces entered for indent tabs.
|
jpayne@68
|
509 Changing indent_scale value with the mouse sets Var space_num,
|
jpayne@68
|
510 which invokes the default callback to add an entry to
|
jpayne@68
|
511 changes. Load_tab_cfg initializes space_num to default.
|
jpayne@68
|
512
|
jpayne@68
|
513 Widgets for FontPage(Frame): (*) widgets bound to self
|
jpayne@68
|
514 frame_font: LabelFrame
|
jpayne@68
|
515 frame_font_name: Frame
|
jpayne@68
|
516 font_name_title: Label
|
jpayne@68
|
517 (*)fontlist: ListBox - font_name
|
jpayne@68
|
518 scroll_font: Scrollbar
|
jpayne@68
|
519 frame_font_param: Frame
|
jpayne@68
|
520 font_size_title: Label
|
jpayne@68
|
521 (*)sizelist: DynOptionMenu - font_size
|
jpayne@68
|
522 (*)bold_toggle: Checkbutton - font_bold
|
jpayne@68
|
523 frame_sample: LabelFrame
|
jpayne@68
|
524 (*)font_sample: Label
|
jpayne@68
|
525 frame_indent: LabelFrame
|
jpayne@68
|
526 indent_title: Label
|
jpayne@68
|
527 (*)indent_scale: Scale - space_num
|
jpayne@68
|
528 """
|
jpayne@68
|
529 self.font_name = tracers.add(StringVar(self), self.var_changed_font)
|
jpayne@68
|
530 self.font_size = tracers.add(StringVar(self), self.var_changed_font)
|
jpayne@68
|
531 self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
|
jpayne@68
|
532 self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
|
jpayne@68
|
533
|
jpayne@68
|
534 # Define frames and widgets.
|
jpayne@68
|
535 frame_font = LabelFrame(
|
jpayne@68
|
536 self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ')
|
jpayne@68
|
537 frame_sample = LabelFrame(
|
jpayne@68
|
538 self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
539 text=' Font Sample (Editable) ')
|
jpayne@68
|
540 frame_indent = LabelFrame(
|
jpayne@68
|
541 self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
|
jpayne@68
|
542 # frame_font.
|
jpayne@68
|
543 frame_font_name = Frame(frame_font)
|
jpayne@68
|
544 frame_font_param = Frame(frame_font)
|
jpayne@68
|
545 font_name_title = Label(
|
jpayne@68
|
546 frame_font_name, justify=LEFT, text='Font Face :')
|
jpayne@68
|
547 self.fontlist = Listbox(frame_font_name, height=15,
|
jpayne@68
|
548 takefocus=True, exportselection=FALSE)
|
jpayne@68
|
549 self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
|
jpayne@68
|
550 self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
|
jpayne@68
|
551 self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
|
jpayne@68
|
552 scroll_font = Scrollbar(frame_font_name)
|
jpayne@68
|
553 scroll_font.config(command=self.fontlist.yview)
|
jpayne@68
|
554 self.fontlist.config(yscrollcommand=scroll_font.set)
|
jpayne@68
|
555 font_size_title = Label(frame_font_param, text='Size :')
|
jpayne@68
|
556 self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
|
jpayne@68
|
557 self.bold_toggle = Checkbutton(
|
jpayne@68
|
558 frame_font_param, variable=self.font_bold,
|
jpayne@68
|
559 onvalue=1, offvalue=0, text='Bold')
|
jpayne@68
|
560 # frame_sample.
|
jpayne@68
|
561 font_sample_frame = ScrollableTextFrame(frame_sample)
|
jpayne@68
|
562 self.font_sample = font_sample_frame.text
|
jpayne@68
|
563 self.font_sample.config(wrap=NONE, width=1, height=1)
|
jpayne@68
|
564 self.font_sample.insert(END, font_sample_text)
|
jpayne@68
|
565 # frame_indent.
|
jpayne@68
|
566 indent_title = Label(
|
jpayne@68
|
567 frame_indent, justify=LEFT,
|
jpayne@68
|
568 text='Python Standard: 4 Spaces!')
|
jpayne@68
|
569 self.indent_scale = Scale(
|
jpayne@68
|
570 frame_indent, variable=self.space_num,
|
jpayne@68
|
571 orient='horizontal', tickinterval=2, from_=2, to=16)
|
jpayne@68
|
572
|
jpayne@68
|
573 # Grid and pack widgets:
|
jpayne@68
|
574 self.columnconfigure(1, weight=1)
|
jpayne@68
|
575 self.rowconfigure(2, weight=1)
|
jpayne@68
|
576 frame_font.grid(row=0, column=0, padx=5, pady=5)
|
jpayne@68
|
577 frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
|
jpayne@68
|
578 sticky='nsew')
|
jpayne@68
|
579 frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
|
jpayne@68
|
580 # frame_font.
|
jpayne@68
|
581 frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
|
jpayne@68
|
582 frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
|
jpayne@68
|
583 font_name_title.pack(side=TOP, anchor=W)
|
jpayne@68
|
584 self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
|
jpayne@68
|
585 scroll_font.pack(side=LEFT, fill=Y)
|
jpayne@68
|
586 font_size_title.pack(side=LEFT, anchor=W)
|
jpayne@68
|
587 self.sizelist.pack(side=LEFT, anchor=W)
|
jpayne@68
|
588 self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
|
jpayne@68
|
589 # frame_sample.
|
jpayne@68
|
590 font_sample_frame.pack(expand=TRUE, fill=BOTH)
|
jpayne@68
|
591 # frame_indent.
|
jpayne@68
|
592 indent_title.pack(side=TOP, anchor=W, padx=5)
|
jpayne@68
|
593 self.indent_scale.pack(side=TOP, padx=5, fill=X)
|
jpayne@68
|
594
|
jpayne@68
|
595 def load_font_cfg(self):
|
jpayne@68
|
596 """Load current configuration settings for the font options.
|
jpayne@68
|
597
|
jpayne@68
|
598 Retrieve current font with idleConf.GetFont and font families
|
jpayne@68
|
599 from tk. Setup fontlist and set font_name. Setup sizelist,
|
jpayne@68
|
600 which sets font_size. Set font_bold. Call set_samples.
|
jpayne@68
|
601 """
|
jpayne@68
|
602 configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
|
jpayne@68
|
603 font_name = configured_font[0].lower()
|
jpayne@68
|
604 font_size = configured_font[1]
|
jpayne@68
|
605 font_bold = configured_font[2]=='bold'
|
jpayne@68
|
606
|
jpayne@68
|
607 # Set editor font selection list and font_name.
|
jpayne@68
|
608 fonts = list(tkFont.families(self))
|
jpayne@68
|
609 fonts.sort()
|
jpayne@68
|
610 for font in fonts:
|
jpayne@68
|
611 self.fontlist.insert(END, font)
|
jpayne@68
|
612 self.font_name.set(font_name)
|
jpayne@68
|
613 lc_fonts = [s.lower() for s in fonts]
|
jpayne@68
|
614 try:
|
jpayne@68
|
615 current_font_index = lc_fonts.index(font_name)
|
jpayne@68
|
616 self.fontlist.see(current_font_index)
|
jpayne@68
|
617 self.fontlist.select_set(current_font_index)
|
jpayne@68
|
618 self.fontlist.select_anchor(current_font_index)
|
jpayne@68
|
619 self.fontlist.activate(current_font_index)
|
jpayne@68
|
620 except ValueError:
|
jpayne@68
|
621 pass
|
jpayne@68
|
622 # Set font size dropdown.
|
jpayne@68
|
623 self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
|
jpayne@68
|
624 '16', '18', '20', '22', '25', '29', '34', '40'),
|
jpayne@68
|
625 font_size)
|
jpayne@68
|
626 # Set font weight.
|
jpayne@68
|
627 self.font_bold.set(font_bold)
|
jpayne@68
|
628 self.set_samples()
|
jpayne@68
|
629
|
jpayne@68
|
630 def var_changed_font(self, *params):
|
jpayne@68
|
631 """Store changes to font attributes.
|
jpayne@68
|
632
|
jpayne@68
|
633 When one font attribute changes, save them all, as they are
|
jpayne@68
|
634 not independent from each other. In particular, when we are
|
jpayne@68
|
635 overriding the default font, we need to write out everything.
|
jpayne@68
|
636 """
|
jpayne@68
|
637 value = self.font_name.get()
|
jpayne@68
|
638 changes.add_option('main', 'EditorWindow', 'font', value)
|
jpayne@68
|
639 value = self.font_size.get()
|
jpayne@68
|
640 changes.add_option('main', 'EditorWindow', 'font-size', value)
|
jpayne@68
|
641 value = self.font_bold.get()
|
jpayne@68
|
642 changes.add_option('main', 'EditorWindow', 'font-bold', value)
|
jpayne@68
|
643 self.set_samples()
|
jpayne@68
|
644
|
jpayne@68
|
645 def on_fontlist_select(self, event):
|
jpayne@68
|
646 """Handle selecting a font from the list.
|
jpayne@68
|
647
|
jpayne@68
|
648 Event can result from either mouse click or Up or Down key.
|
jpayne@68
|
649 Set font_name and example displays to selection.
|
jpayne@68
|
650 """
|
jpayne@68
|
651 font = self.fontlist.get(
|
jpayne@68
|
652 ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
|
jpayne@68
|
653 self.font_name.set(font.lower())
|
jpayne@68
|
654
|
jpayne@68
|
655 def set_samples(self, event=None):
|
jpayne@68
|
656 """Update update both screen samples with the font settings.
|
jpayne@68
|
657
|
jpayne@68
|
658 Called on font initialization and change events.
|
jpayne@68
|
659 Accesses font_name, font_size, and font_bold Variables.
|
jpayne@68
|
660 Updates font_sample and highlight page highlight_sample.
|
jpayne@68
|
661 """
|
jpayne@68
|
662 font_name = self.font_name.get()
|
jpayne@68
|
663 font_weight = tkFont.BOLD if self.font_bold.get() else tkFont.NORMAL
|
jpayne@68
|
664 new_font = (font_name, self.font_size.get(), font_weight)
|
jpayne@68
|
665 self.font_sample['font'] = new_font
|
jpayne@68
|
666 self.highlight_sample['font'] = new_font
|
jpayne@68
|
667
|
jpayne@68
|
668 def load_tab_cfg(self):
|
jpayne@68
|
669 """Load current configuration settings for the tab options.
|
jpayne@68
|
670
|
jpayne@68
|
671 Attributes updated:
|
jpayne@68
|
672 space_num: Set to value from idleConf.
|
jpayne@68
|
673 """
|
jpayne@68
|
674 # Set indent sizes.
|
jpayne@68
|
675 space_num = idleConf.GetOption(
|
jpayne@68
|
676 'main', 'Indent', 'num-spaces', default=4, type='int')
|
jpayne@68
|
677 self.space_num.set(space_num)
|
jpayne@68
|
678
|
jpayne@68
|
679 def var_changed_space_num(self, *params):
|
jpayne@68
|
680 "Store change to indentation size."
|
jpayne@68
|
681 value = self.space_num.get()
|
jpayne@68
|
682 changes.add_option('main', 'Indent', 'num-spaces', value)
|
jpayne@68
|
683
|
jpayne@68
|
684
|
jpayne@68
|
685 class HighPage(Frame):
|
jpayne@68
|
686
|
jpayne@68
|
687 def __init__(self, master):
|
jpayne@68
|
688 super().__init__(master)
|
jpayne@68
|
689 self.cd = master.master
|
jpayne@68
|
690 self.style = Style(master)
|
jpayne@68
|
691 self.create_page_highlight()
|
jpayne@68
|
692 self.load_theme_cfg()
|
jpayne@68
|
693
|
jpayne@68
|
694 def create_page_highlight(self):
|
jpayne@68
|
695 """Return frame of widgets for Highlighting tab.
|
jpayne@68
|
696
|
jpayne@68
|
697 Enable users to provisionally change foreground and background
|
jpayne@68
|
698 colors applied to textual tags. Color mappings are stored in
|
jpayne@68
|
699 complete listings called themes. Built-in themes in
|
jpayne@68
|
700 idlelib/config-highlight.def are fixed as far as the dialog is
|
jpayne@68
|
701 concerned. Any theme can be used as the base for a new custom
|
jpayne@68
|
702 theme, stored in .idlerc/config-highlight.cfg.
|
jpayne@68
|
703
|
jpayne@68
|
704 Function load_theme_cfg() initializes tk variables and theme
|
jpayne@68
|
705 lists and calls paint_theme_sample() and set_highlight_target()
|
jpayne@68
|
706 for the current theme. Radiobuttons builtin_theme_on and
|
jpayne@68
|
707 custom_theme_on toggle var theme_source, which controls if the
|
jpayne@68
|
708 current set of colors are from a builtin or custom theme.
|
jpayne@68
|
709 DynOptionMenus builtinlist and customlist contain lists of the
|
jpayne@68
|
710 builtin and custom themes, respectively, and the current item
|
jpayne@68
|
711 from each list is stored in vars builtin_name and custom_name.
|
jpayne@68
|
712
|
jpayne@68
|
713 Function paint_theme_sample() applies the colors from the theme
|
jpayne@68
|
714 to the tags in text widget highlight_sample and then invokes
|
jpayne@68
|
715 set_color_sample(). Function set_highlight_target() sets the state
|
jpayne@68
|
716 of the radiobuttons fg_on and bg_on based on the tag and it also
|
jpayne@68
|
717 invokes set_color_sample().
|
jpayne@68
|
718
|
jpayne@68
|
719 Function set_color_sample() sets the background color for the frame
|
jpayne@68
|
720 holding the color selector. This provides a larger visual of the
|
jpayne@68
|
721 color for the current tag and plane (foreground/background).
|
jpayne@68
|
722
|
jpayne@68
|
723 Note: set_color_sample() is called from many places and is often
|
jpayne@68
|
724 called more than once when a change is made. It is invoked when
|
jpayne@68
|
725 foreground or background is selected (radiobuttons), from
|
jpayne@68
|
726 paint_theme_sample() (theme is changed or load_cfg is called), and
|
jpayne@68
|
727 from set_highlight_target() (target tag is changed or load_cfg called).
|
jpayne@68
|
728
|
jpayne@68
|
729 Button delete_custom invokes delete_custom() to delete
|
jpayne@68
|
730 a custom theme from idleConf.userCfg['highlight'] and changes.
|
jpayne@68
|
731 Button save_custom invokes save_as_new_theme() which calls
|
jpayne@68
|
732 get_new_theme_name() and create_new() to save a custom theme
|
jpayne@68
|
733 and its colors to idleConf.userCfg['highlight'].
|
jpayne@68
|
734
|
jpayne@68
|
735 Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
|
jpayne@68
|
736 if the current selected color for a tag is for the foreground or
|
jpayne@68
|
737 background.
|
jpayne@68
|
738
|
jpayne@68
|
739 DynOptionMenu targetlist contains a readable description of the
|
jpayne@68
|
740 tags applied to Python source within IDLE. Selecting one of the
|
jpayne@68
|
741 tags from this list populates highlight_target, which has a callback
|
jpayne@68
|
742 function set_highlight_target().
|
jpayne@68
|
743
|
jpayne@68
|
744 Text widget highlight_sample displays a block of text (which is
|
jpayne@68
|
745 mock Python code) in which is embedded the defined tags and reflects
|
jpayne@68
|
746 the color attributes of the current theme and changes for those tags.
|
jpayne@68
|
747 Mouse button 1 allows for selection of a tag and updates
|
jpayne@68
|
748 highlight_target with that tag value.
|
jpayne@68
|
749
|
jpayne@68
|
750 Note: The font in highlight_sample is set through the config in
|
jpayne@68
|
751 the fonts tab.
|
jpayne@68
|
752
|
jpayne@68
|
753 In other words, a tag can be selected either from targetlist or
|
jpayne@68
|
754 by clicking on the sample text within highlight_sample. The
|
jpayne@68
|
755 plane (foreground/background) is selected via the radiobutton.
|
jpayne@68
|
756 Together, these two (tag and plane) control what color is
|
jpayne@68
|
757 shown in set_color_sample() for the current theme. Button set_color
|
jpayne@68
|
758 invokes get_color() which displays a ColorChooser to change the
|
jpayne@68
|
759 color for the selected tag/plane. If a new color is picked,
|
jpayne@68
|
760 it will be saved to changes and the highlight_sample and
|
jpayne@68
|
761 frame background will be updated.
|
jpayne@68
|
762
|
jpayne@68
|
763 Tk Variables:
|
jpayne@68
|
764 color: Color of selected target.
|
jpayne@68
|
765 builtin_name: Menu variable for built-in theme.
|
jpayne@68
|
766 custom_name: Menu variable for custom theme.
|
jpayne@68
|
767 fg_bg_toggle: Toggle for foreground/background color.
|
jpayne@68
|
768 Note: this has no callback.
|
jpayne@68
|
769 theme_source: Selector for built-in or custom theme.
|
jpayne@68
|
770 highlight_target: Menu variable for the highlight tag target.
|
jpayne@68
|
771
|
jpayne@68
|
772 Instance Data Attributes:
|
jpayne@68
|
773 theme_elements: Dictionary of tags for text highlighting.
|
jpayne@68
|
774 The key is the display name and the value is a tuple of
|
jpayne@68
|
775 (tag name, display sort order).
|
jpayne@68
|
776
|
jpayne@68
|
777 Methods [attachment]:
|
jpayne@68
|
778 load_theme_cfg: Load current highlight colors.
|
jpayne@68
|
779 get_color: Invoke colorchooser [button_set_color].
|
jpayne@68
|
780 set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
|
jpayne@68
|
781 set_highlight_target: set fg_bg_toggle, set_color_sample().
|
jpayne@68
|
782 set_color_sample: Set frame background to target.
|
jpayne@68
|
783 on_new_color_set: Set new color and add option.
|
jpayne@68
|
784 paint_theme_sample: Recolor sample.
|
jpayne@68
|
785 get_new_theme_name: Get from popup.
|
jpayne@68
|
786 create_new: Combine theme with changes and save.
|
jpayne@68
|
787 save_as_new_theme: Save [button_save_custom].
|
jpayne@68
|
788 set_theme_type: Command for [theme_source].
|
jpayne@68
|
789 delete_custom: Activate default [button_delete_custom].
|
jpayne@68
|
790 save_new: Save to userCfg['theme'] (is function).
|
jpayne@68
|
791
|
jpayne@68
|
792 Widgets of highlights page frame: (*) widgets bound to self
|
jpayne@68
|
793 frame_custom: LabelFrame
|
jpayne@68
|
794 (*)highlight_sample: Text
|
jpayne@68
|
795 (*)frame_color_set: Frame
|
jpayne@68
|
796 (*)button_set_color: Button
|
jpayne@68
|
797 (*)targetlist: DynOptionMenu - highlight_target
|
jpayne@68
|
798 frame_fg_bg_toggle: Frame
|
jpayne@68
|
799 (*)fg_on: Radiobutton - fg_bg_toggle
|
jpayne@68
|
800 (*)bg_on: Radiobutton - fg_bg_toggle
|
jpayne@68
|
801 (*)button_save_custom: Button
|
jpayne@68
|
802 frame_theme: LabelFrame
|
jpayne@68
|
803 theme_type_title: Label
|
jpayne@68
|
804 (*)builtin_theme_on: Radiobutton - theme_source
|
jpayne@68
|
805 (*)custom_theme_on: Radiobutton - theme_source
|
jpayne@68
|
806 (*)builtinlist: DynOptionMenu - builtin_name
|
jpayne@68
|
807 (*)customlist: DynOptionMenu - custom_name
|
jpayne@68
|
808 (*)button_delete_custom: Button
|
jpayne@68
|
809 (*)theme_message: Label
|
jpayne@68
|
810 """
|
jpayne@68
|
811 self.theme_elements = {
|
jpayne@68
|
812 'Normal Code or Text': ('normal', '00'),
|
jpayne@68
|
813 'Code Context': ('context', '01'),
|
jpayne@68
|
814 'Python Keywords': ('keyword', '02'),
|
jpayne@68
|
815 'Python Definitions': ('definition', '03'),
|
jpayne@68
|
816 'Python Builtins': ('builtin', '04'),
|
jpayne@68
|
817 'Python Comments': ('comment', '05'),
|
jpayne@68
|
818 'Python Strings': ('string', '06'),
|
jpayne@68
|
819 'Selected Text': ('hilite', '07'),
|
jpayne@68
|
820 'Found Text': ('hit', '08'),
|
jpayne@68
|
821 'Cursor': ('cursor', '09'),
|
jpayne@68
|
822 'Editor Breakpoint': ('break', '10'),
|
jpayne@68
|
823 'Shell Prompt': ('console', '11'),
|
jpayne@68
|
824 'Error Text': ('error', '12'),
|
jpayne@68
|
825 'Shell User Output': ('stdout', '13'),
|
jpayne@68
|
826 'Shell User Exception': ('stderr', '14'),
|
jpayne@68
|
827 'Line Number': ('linenumber', '16'),
|
jpayne@68
|
828 }
|
jpayne@68
|
829 self.builtin_name = tracers.add(
|
jpayne@68
|
830 StringVar(self), self.var_changed_builtin_name)
|
jpayne@68
|
831 self.custom_name = tracers.add(
|
jpayne@68
|
832 StringVar(self), self.var_changed_custom_name)
|
jpayne@68
|
833 self.fg_bg_toggle = BooleanVar(self)
|
jpayne@68
|
834 self.color = tracers.add(
|
jpayne@68
|
835 StringVar(self), self.var_changed_color)
|
jpayne@68
|
836 self.theme_source = tracers.add(
|
jpayne@68
|
837 BooleanVar(self), self.var_changed_theme_source)
|
jpayne@68
|
838 self.highlight_target = tracers.add(
|
jpayne@68
|
839 StringVar(self), self.var_changed_highlight_target)
|
jpayne@68
|
840
|
jpayne@68
|
841 # Create widgets:
|
jpayne@68
|
842 # body frame and section frames.
|
jpayne@68
|
843 frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
844 text=' Custom Highlighting ')
|
jpayne@68
|
845 frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
846 text=' Highlighting Theme ')
|
jpayne@68
|
847 # frame_custom.
|
jpayne@68
|
848 sample_frame = ScrollableTextFrame(
|
jpayne@68
|
849 frame_custom, relief=SOLID, borderwidth=1)
|
jpayne@68
|
850 text = self.highlight_sample = sample_frame.text
|
jpayne@68
|
851 text.configure(
|
jpayne@68
|
852 font=('courier', 12, ''), cursor='hand2', width=1, height=1,
|
jpayne@68
|
853 takefocus=FALSE, highlightthickness=0, wrap=NONE)
|
jpayne@68
|
854 text.bind('<Double-Button-1>', lambda e: 'break')
|
jpayne@68
|
855 text.bind('<B1-Motion>', lambda e: 'break')
|
jpayne@68
|
856 string_tags=(
|
jpayne@68
|
857 ('# Click selects item.', 'comment'), ('\n', 'normal'),
|
jpayne@68
|
858 ('code context section', 'context'), ('\n', 'normal'),
|
jpayne@68
|
859 ('| cursor', 'cursor'), ('\n', 'normal'),
|
jpayne@68
|
860 ('def', 'keyword'), (' ', 'normal'),
|
jpayne@68
|
861 ('func', 'definition'), ('(param):\n ', 'normal'),
|
jpayne@68
|
862 ('"Return None."', 'string'), ('\n var0 = ', 'normal'),
|
jpayne@68
|
863 ("'string'", 'string'), ('\n var1 = ', 'normal'),
|
jpayne@68
|
864 ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
|
jpayne@68
|
865 ("'found'", 'hit'), ('\n var3 = ', 'normal'),
|
jpayne@68
|
866 ('list', 'builtin'), ('(', 'normal'),
|
jpayne@68
|
867 ('None', 'keyword'), (')\n', 'normal'),
|
jpayne@68
|
868 (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
|
jpayne@68
|
869 ('>>>', 'console'), (' 3.14**2\n', 'normal'),
|
jpayne@68
|
870 ('9.8596', 'stdout'), ('\n', 'normal'),
|
jpayne@68
|
871 ('>>>', 'console'), (' pri ', 'normal'),
|
jpayne@68
|
872 ('n', 'error'), ('t(\n', 'normal'),
|
jpayne@68
|
873 ('SyntaxError', 'stderr'), ('\n', 'normal'))
|
jpayne@68
|
874 for string, tag in string_tags:
|
jpayne@68
|
875 text.insert(END, string, tag)
|
jpayne@68
|
876 n_lines = len(text.get('1.0', END).splitlines())
|
jpayne@68
|
877 for lineno in range(1, n_lines):
|
jpayne@68
|
878 text.insert(f'{lineno}.0',
|
jpayne@68
|
879 f'{lineno:{len(str(n_lines))}d} ',
|
jpayne@68
|
880 'linenumber')
|
jpayne@68
|
881 for element in self.theme_elements:
|
jpayne@68
|
882 def tem(event, elem=element):
|
jpayne@68
|
883 # event.widget.winfo_top_level().highlight_target.set(elem)
|
jpayne@68
|
884 self.highlight_target.set(elem)
|
jpayne@68
|
885 text.tag_bind(
|
jpayne@68
|
886 self.theme_elements[element][0], '<ButtonPress-1>', tem)
|
jpayne@68
|
887 text['state'] = 'disabled'
|
jpayne@68
|
888 self.style.configure('frame_color_set.TFrame', borderwidth=1,
|
jpayne@68
|
889 relief='solid')
|
jpayne@68
|
890 self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
|
jpayne@68
|
891 frame_fg_bg_toggle = Frame(frame_custom)
|
jpayne@68
|
892 self.button_set_color = Button(
|
jpayne@68
|
893 self.frame_color_set, text='Choose Color for :',
|
jpayne@68
|
894 command=self.get_color)
|
jpayne@68
|
895 self.targetlist = DynOptionMenu(
|
jpayne@68
|
896 self.frame_color_set, self.highlight_target, None,
|
jpayne@68
|
897 highlightthickness=0) #, command=self.set_highlight_targetBinding
|
jpayne@68
|
898 self.fg_on = Radiobutton(
|
jpayne@68
|
899 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
|
jpayne@68
|
900 text='Foreground', command=self.set_color_sample_binding)
|
jpayne@68
|
901 self.bg_on = Radiobutton(
|
jpayne@68
|
902 frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
|
jpayne@68
|
903 text='Background', command=self.set_color_sample_binding)
|
jpayne@68
|
904 self.fg_bg_toggle.set(1)
|
jpayne@68
|
905 self.button_save_custom = Button(
|
jpayne@68
|
906 frame_custom, text='Save as New Custom Theme',
|
jpayne@68
|
907 command=self.save_as_new_theme)
|
jpayne@68
|
908 # frame_theme.
|
jpayne@68
|
909 theme_type_title = Label(frame_theme, text='Select : ')
|
jpayne@68
|
910 self.builtin_theme_on = Radiobutton(
|
jpayne@68
|
911 frame_theme, variable=self.theme_source, value=1,
|
jpayne@68
|
912 command=self.set_theme_type, text='a Built-in Theme')
|
jpayne@68
|
913 self.custom_theme_on = Radiobutton(
|
jpayne@68
|
914 frame_theme, variable=self.theme_source, value=0,
|
jpayne@68
|
915 command=self.set_theme_type, text='a Custom Theme')
|
jpayne@68
|
916 self.builtinlist = DynOptionMenu(
|
jpayne@68
|
917 frame_theme, self.builtin_name, None, command=None)
|
jpayne@68
|
918 self.customlist = DynOptionMenu(
|
jpayne@68
|
919 frame_theme, self.custom_name, None, command=None)
|
jpayne@68
|
920 self.button_delete_custom = Button(
|
jpayne@68
|
921 frame_theme, text='Delete Custom Theme',
|
jpayne@68
|
922 command=self.delete_custom)
|
jpayne@68
|
923 self.theme_message = Label(frame_theme, borderwidth=2)
|
jpayne@68
|
924 # Pack widgets:
|
jpayne@68
|
925 # body.
|
jpayne@68
|
926 frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
927 frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
|
jpayne@68
|
928 # frame_custom.
|
jpayne@68
|
929 self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
|
jpayne@68
|
930 frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
|
jpayne@68
|
931 sample_frame.pack(
|
jpayne@68
|
932 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
933 self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
|
jpayne@68
|
934 self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
|
jpayne@68
|
935 self.fg_on.pack(side=LEFT, anchor=E)
|
jpayne@68
|
936 self.bg_on.pack(side=RIGHT, anchor=W)
|
jpayne@68
|
937 self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
jpayne@68
|
938 # frame_theme.
|
jpayne@68
|
939 theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
|
jpayne@68
|
940 self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
|
jpayne@68
|
941 self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
|
jpayne@68
|
942 self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
|
jpayne@68
|
943 self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
|
jpayne@68
|
944 self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
|
jpayne@68
|
945 self.theme_message.pack(side=TOP, fill=X, pady=5)
|
jpayne@68
|
946
|
jpayne@68
|
947 def load_theme_cfg(self):
|
jpayne@68
|
948 """Load current configuration settings for the theme options.
|
jpayne@68
|
949
|
jpayne@68
|
950 Based on the theme_source toggle, the theme is set as
|
jpayne@68
|
951 either builtin or custom and the initial widget values
|
jpayne@68
|
952 reflect the current settings from idleConf.
|
jpayne@68
|
953
|
jpayne@68
|
954 Attributes updated:
|
jpayne@68
|
955 theme_source: Set from idleConf.
|
jpayne@68
|
956 builtinlist: List of default themes from idleConf.
|
jpayne@68
|
957 customlist: List of custom themes from idleConf.
|
jpayne@68
|
958 custom_theme_on: Disabled if there are no custom themes.
|
jpayne@68
|
959 custom_theme: Message with additional information.
|
jpayne@68
|
960 targetlist: Create menu from self.theme_elements.
|
jpayne@68
|
961
|
jpayne@68
|
962 Methods:
|
jpayne@68
|
963 set_theme_type
|
jpayne@68
|
964 paint_theme_sample
|
jpayne@68
|
965 set_highlight_target
|
jpayne@68
|
966 """
|
jpayne@68
|
967 # Set current theme type radiobutton.
|
jpayne@68
|
968 self.theme_source.set(idleConf.GetOption(
|
jpayne@68
|
969 'main', 'Theme', 'default', type='bool', default=1))
|
jpayne@68
|
970 # Set current theme.
|
jpayne@68
|
971 current_option = idleConf.CurrentTheme()
|
jpayne@68
|
972 # Load available theme option menus.
|
jpayne@68
|
973 if self.theme_source.get(): # Default theme selected.
|
jpayne@68
|
974 item_list = idleConf.GetSectionList('default', 'highlight')
|
jpayne@68
|
975 item_list.sort()
|
jpayne@68
|
976 self.builtinlist.SetMenu(item_list, current_option)
|
jpayne@68
|
977 item_list = idleConf.GetSectionList('user', 'highlight')
|
jpayne@68
|
978 item_list.sort()
|
jpayne@68
|
979 if not item_list:
|
jpayne@68
|
980 self.custom_theme_on.state(('disabled',))
|
jpayne@68
|
981 self.custom_name.set('- no custom themes -')
|
jpayne@68
|
982 else:
|
jpayne@68
|
983 self.customlist.SetMenu(item_list, item_list[0])
|
jpayne@68
|
984 else: # User theme selected.
|
jpayne@68
|
985 item_list = idleConf.GetSectionList('user', 'highlight')
|
jpayne@68
|
986 item_list.sort()
|
jpayne@68
|
987 self.customlist.SetMenu(item_list, current_option)
|
jpayne@68
|
988 item_list = idleConf.GetSectionList('default', 'highlight')
|
jpayne@68
|
989 item_list.sort()
|
jpayne@68
|
990 self.builtinlist.SetMenu(item_list, item_list[0])
|
jpayne@68
|
991 self.set_theme_type()
|
jpayne@68
|
992 # Load theme element option menu.
|
jpayne@68
|
993 theme_names = list(self.theme_elements.keys())
|
jpayne@68
|
994 theme_names.sort(key=lambda x: self.theme_elements[x][1])
|
jpayne@68
|
995 self.targetlist.SetMenu(theme_names, theme_names[0])
|
jpayne@68
|
996 self.paint_theme_sample()
|
jpayne@68
|
997 self.set_highlight_target()
|
jpayne@68
|
998
|
jpayne@68
|
999 def var_changed_builtin_name(self, *params):
|
jpayne@68
|
1000 """Process new builtin theme selection.
|
jpayne@68
|
1001
|
jpayne@68
|
1002 Add the changed theme's name to the changed_items and recreate
|
jpayne@68
|
1003 the sample with the values from the selected theme.
|
jpayne@68
|
1004 """
|
jpayne@68
|
1005 old_themes = ('IDLE Classic', 'IDLE New')
|
jpayne@68
|
1006 value = self.builtin_name.get()
|
jpayne@68
|
1007 if value not in old_themes:
|
jpayne@68
|
1008 if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
|
jpayne@68
|
1009 changes.add_option('main', 'Theme', 'name', old_themes[0])
|
jpayne@68
|
1010 changes.add_option('main', 'Theme', 'name2', value)
|
jpayne@68
|
1011 self.theme_message['text'] = 'New theme, see Help'
|
jpayne@68
|
1012 else:
|
jpayne@68
|
1013 changes.add_option('main', 'Theme', 'name', value)
|
jpayne@68
|
1014 changes.add_option('main', 'Theme', 'name2', '')
|
jpayne@68
|
1015 self.theme_message['text'] = ''
|
jpayne@68
|
1016 self.paint_theme_sample()
|
jpayne@68
|
1017
|
jpayne@68
|
1018 def var_changed_custom_name(self, *params):
|
jpayne@68
|
1019 """Process new custom theme selection.
|
jpayne@68
|
1020
|
jpayne@68
|
1021 If a new custom theme is selected, add the name to the
|
jpayne@68
|
1022 changed_items and apply the theme to the sample.
|
jpayne@68
|
1023 """
|
jpayne@68
|
1024 value = self.custom_name.get()
|
jpayne@68
|
1025 if value != '- no custom themes -':
|
jpayne@68
|
1026 changes.add_option('main', 'Theme', 'name', value)
|
jpayne@68
|
1027 self.paint_theme_sample()
|
jpayne@68
|
1028
|
jpayne@68
|
1029 def var_changed_theme_source(self, *params):
|
jpayne@68
|
1030 """Process toggle between builtin and custom theme.
|
jpayne@68
|
1031
|
jpayne@68
|
1032 Update the default toggle value and apply the newly
|
jpayne@68
|
1033 selected theme type.
|
jpayne@68
|
1034 """
|
jpayne@68
|
1035 value = self.theme_source.get()
|
jpayne@68
|
1036 changes.add_option('main', 'Theme', 'default', value)
|
jpayne@68
|
1037 if value:
|
jpayne@68
|
1038 self.var_changed_builtin_name()
|
jpayne@68
|
1039 else:
|
jpayne@68
|
1040 self.var_changed_custom_name()
|
jpayne@68
|
1041
|
jpayne@68
|
1042 def var_changed_color(self, *params):
|
jpayne@68
|
1043 "Process change to color choice."
|
jpayne@68
|
1044 self.on_new_color_set()
|
jpayne@68
|
1045
|
jpayne@68
|
1046 def var_changed_highlight_target(self, *params):
|
jpayne@68
|
1047 "Process selection of new target tag for highlighting."
|
jpayne@68
|
1048 self.set_highlight_target()
|
jpayne@68
|
1049
|
jpayne@68
|
1050 def set_theme_type(self):
|
jpayne@68
|
1051 """Set available screen options based on builtin or custom theme.
|
jpayne@68
|
1052
|
jpayne@68
|
1053 Attributes accessed:
|
jpayne@68
|
1054 theme_source
|
jpayne@68
|
1055
|
jpayne@68
|
1056 Attributes updated:
|
jpayne@68
|
1057 builtinlist
|
jpayne@68
|
1058 customlist
|
jpayne@68
|
1059 button_delete_custom
|
jpayne@68
|
1060 custom_theme_on
|
jpayne@68
|
1061
|
jpayne@68
|
1062 Called from:
|
jpayne@68
|
1063 handler for builtin_theme_on and custom_theme_on
|
jpayne@68
|
1064 delete_custom
|
jpayne@68
|
1065 create_new
|
jpayne@68
|
1066 load_theme_cfg
|
jpayne@68
|
1067 """
|
jpayne@68
|
1068 if self.theme_source.get():
|
jpayne@68
|
1069 self.builtinlist['state'] = 'normal'
|
jpayne@68
|
1070 self.customlist['state'] = 'disabled'
|
jpayne@68
|
1071 self.button_delete_custom.state(('disabled',))
|
jpayne@68
|
1072 else:
|
jpayne@68
|
1073 self.builtinlist['state'] = 'disabled'
|
jpayne@68
|
1074 self.custom_theme_on.state(('!disabled',))
|
jpayne@68
|
1075 self.customlist['state'] = 'normal'
|
jpayne@68
|
1076 self.button_delete_custom.state(('!disabled',))
|
jpayne@68
|
1077
|
jpayne@68
|
1078 def get_color(self):
|
jpayne@68
|
1079 """Handle button to select a new color for the target tag.
|
jpayne@68
|
1080
|
jpayne@68
|
1081 If a new color is selected while using a builtin theme, a
|
jpayne@68
|
1082 name must be supplied to create a custom theme.
|
jpayne@68
|
1083
|
jpayne@68
|
1084 Attributes accessed:
|
jpayne@68
|
1085 highlight_target
|
jpayne@68
|
1086 frame_color_set
|
jpayne@68
|
1087 theme_source
|
jpayne@68
|
1088
|
jpayne@68
|
1089 Attributes updated:
|
jpayne@68
|
1090 color
|
jpayne@68
|
1091
|
jpayne@68
|
1092 Methods:
|
jpayne@68
|
1093 get_new_theme_name
|
jpayne@68
|
1094 create_new
|
jpayne@68
|
1095 """
|
jpayne@68
|
1096 target = self.highlight_target.get()
|
jpayne@68
|
1097 prev_color = self.style.lookup(self.frame_color_set['style'],
|
jpayne@68
|
1098 'background')
|
jpayne@68
|
1099 rgbTuplet, color_string = tkColorChooser.askcolor(
|
jpayne@68
|
1100 parent=self, title='Pick new color for : '+target,
|
jpayne@68
|
1101 initialcolor=prev_color)
|
jpayne@68
|
1102 if color_string and (color_string != prev_color):
|
jpayne@68
|
1103 # User didn't cancel and they chose a new color.
|
jpayne@68
|
1104 if self.theme_source.get(): # Current theme is a built-in.
|
jpayne@68
|
1105 message = ('Your changes will be saved as a new Custom Theme. '
|
jpayne@68
|
1106 'Enter a name for your new Custom Theme below.')
|
jpayne@68
|
1107 new_theme = self.get_new_theme_name(message)
|
jpayne@68
|
1108 if not new_theme: # User cancelled custom theme creation.
|
jpayne@68
|
1109 return
|
jpayne@68
|
1110 else: # Create new custom theme based on previously active theme.
|
jpayne@68
|
1111 self.create_new(new_theme)
|
jpayne@68
|
1112 self.color.set(color_string)
|
jpayne@68
|
1113 else: # Current theme is user defined.
|
jpayne@68
|
1114 self.color.set(color_string)
|
jpayne@68
|
1115
|
jpayne@68
|
1116 def on_new_color_set(self):
|
jpayne@68
|
1117 "Display sample of new color selection on the dialog."
|
jpayne@68
|
1118 new_color = self.color.get()
|
jpayne@68
|
1119 self.style.configure('frame_color_set.TFrame', background=new_color)
|
jpayne@68
|
1120 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
|
jpayne@68
|
1121 sample_element = self.theme_elements[self.highlight_target.get()][0]
|
jpayne@68
|
1122 self.highlight_sample.tag_config(sample_element, **{plane: new_color})
|
jpayne@68
|
1123 theme = self.custom_name.get()
|
jpayne@68
|
1124 theme_element = sample_element + '-' + plane
|
jpayne@68
|
1125 changes.add_option('highlight', theme, theme_element, new_color)
|
jpayne@68
|
1126
|
jpayne@68
|
1127 def get_new_theme_name(self, message):
|
jpayne@68
|
1128 "Return name of new theme from query popup."
|
jpayne@68
|
1129 used_names = (idleConf.GetSectionList('user', 'highlight') +
|
jpayne@68
|
1130 idleConf.GetSectionList('default', 'highlight'))
|
jpayne@68
|
1131 new_theme = SectionName(
|
jpayne@68
|
1132 self, 'New Custom Theme', message, used_names).result
|
jpayne@68
|
1133 return new_theme
|
jpayne@68
|
1134
|
jpayne@68
|
1135 def save_as_new_theme(self):
|
jpayne@68
|
1136 """Prompt for new theme name and create the theme.
|
jpayne@68
|
1137
|
jpayne@68
|
1138 Methods:
|
jpayne@68
|
1139 get_new_theme_name
|
jpayne@68
|
1140 create_new
|
jpayne@68
|
1141 """
|
jpayne@68
|
1142 new_theme_name = self.get_new_theme_name('New Theme Name:')
|
jpayne@68
|
1143 if new_theme_name:
|
jpayne@68
|
1144 self.create_new(new_theme_name)
|
jpayne@68
|
1145
|
jpayne@68
|
1146 def create_new(self, new_theme_name):
|
jpayne@68
|
1147 """Create a new custom theme with the given name.
|
jpayne@68
|
1148
|
jpayne@68
|
1149 Create the new theme based on the previously active theme
|
jpayne@68
|
1150 with the current changes applied. Once it is saved, then
|
jpayne@68
|
1151 activate the new theme.
|
jpayne@68
|
1152
|
jpayne@68
|
1153 Attributes accessed:
|
jpayne@68
|
1154 builtin_name
|
jpayne@68
|
1155 custom_name
|
jpayne@68
|
1156
|
jpayne@68
|
1157 Attributes updated:
|
jpayne@68
|
1158 customlist
|
jpayne@68
|
1159 theme_source
|
jpayne@68
|
1160
|
jpayne@68
|
1161 Method:
|
jpayne@68
|
1162 save_new
|
jpayne@68
|
1163 set_theme_type
|
jpayne@68
|
1164 """
|
jpayne@68
|
1165 if self.theme_source.get():
|
jpayne@68
|
1166 theme_type = 'default'
|
jpayne@68
|
1167 theme_name = self.builtin_name.get()
|
jpayne@68
|
1168 else:
|
jpayne@68
|
1169 theme_type = 'user'
|
jpayne@68
|
1170 theme_name = self.custom_name.get()
|
jpayne@68
|
1171 new_theme = idleConf.GetThemeDict(theme_type, theme_name)
|
jpayne@68
|
1172 # Apply any of the old theme's unsaved changes to the new theme.
|
jpayne@68
|
1173 if theme_name in changes['highlight']:
|
jpayne@68
|
1174 theme_changes = changes['highlight'][theme_name]
|
jpayne@68
|
1175 for element in theme_changes:
|
jpayne@68
|
1176 new_theme[element] = theme_changes[element]
|
jpayne@68
|
1177 # Save the new theme.
|
jpayne@68
|
1178 self.save_new(new_theme_name, new_theme)
|
jpayne@68
|
1179 # Change GUI over to the new theme.
|
jpayne@68
|
1180 custom_theme_list = idleConf.GetSectionList('user', 'highlight')
|
jpayne@68
|
1181 custom_theme_list.sort()
|
jpayne@68
|
1182 self.customlist.SetMenu(custom_theme_list, new_theme_name)
|
jpayne@68
|
1183 self.theme_source.set(0)
|
jpayne@68
|
1184 self.set_theme_type()
|
jpayne@68
|
1185
|
jpayne@68
|
1186 def set_highlight_target(self):
|
jpayne@68
|
1187 """Set fg/bg toggle and color based on highlight tag target.
|
jpayne@68
|
1188
|
jpayne@68
|
1189 Instance variables accessed:
|
jpayne@68
|
1190 highlight_target
|
jpayne@68
|
1191
|
jpayne@68
|
1192 Attributes updated:
|
jpayne@68
|
1193 fg_on
|
jpayne@68
|
1194 bg_on
|
jpayne@68
|
1195 fg_bg_toggle
|
jpayne@68
|
1196
|
jpayne@68
|
1197 Methods:
|
jpayne@68
|
1198 set_color_sample
|
jpayne@68
|
1199
|
jpayne@68
|
1200 Called from:
|
jpayne@68
|
1201 var_changed_highlight_target
|
jpayne@68
|
1202 load_theme_cfg
|
jpayne@68
|
1203 """
|
jpayne@68
|
1204 if self.highlight_target.get() == 'Cursor': # bg not possible
|
jpayne@68
|
1205 self.fg_on.state(('disabled',))
|
jpayne@68
|
1206 self.bg_on.state(('disabled',))
|
jpayne@68
|
1207 self.fg_bg_toggle.set(1)
|
jpayne@68
|
1208 else: # Both fg and bg can be set.
|
jpayne@68
|
1209 self.fg_on.state(('!disabled',))
|
jpayne@68
|
1210 self.bg_on.state(('!disabled',))
|
jpayne@68
|
1211 self.fg_bg_toggle.set(1)
|
jpayne@68
|
1212 self.set_color_sample()
|
jpayne@68
|
1213
|
jpayne@68
|
1214 def set_color_sample_binding(self, *args):
|
jpayne@68
|
1215 """Change color sample based on foreground/background toggle.
|
jpayne@68
|
1216
|
jpayne@68
|
1217 Methods:
|
jpayne@68
|
1218 set_color_sample
|
jpayne@68
|
1219 """
|
jpayne@68
|
1220 self.set_color_sample()
|
jpayne@68
|
1221
|
jpayne@68
|
1222 def set_color_sample(self):
|
jpayne@68
|
1223 """Set the color of the frame background to reflect the selected target.
|
jpayne@68
|
1224
|
jpayne@68
|
1225 Instance variables accessed:
|
jpayne@68
|
1226 theme_elements
|
jpayne@68
|
1227 highlight_target
|
jpayne@68
|
1228 fg_bg_toggle
|
jpayne@68
|
1229 highlight_sample
|
jpayne@68
|
1230
|
jpayne@68
|
1231 Attributes updated:
|
jpayne@68
|
1232 frame_color_set
|
jpayne@68
|
1233 """
|
jpayne@68
|
1234 # Set the color sample area.
|
jpayne@68
|
1235 tag = self.theme_elements[self.highlight_target.get()][0]
|
jpayne@68
|
1236 plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
|
jpayne@68
|
1237 color = self.highlight_sample.tag_cget(tag, plane)
|
jpayne@68
|
1238 self.style.configure('frame_color_set.TFrame', background=color)
|
jpayne@68
|
1239
|
jpayne@68
|
1240 def paint_theme_sample(self):
|
jpayne@68
|
1241 """Apply the theme colors to each element tag in the sample text.
|
jpayne@68
|
1242
|
jpayne@68
|
1243 Instance attributes accessed:
|
jpayne@68
|
1244 theme_elements
|
jpayne@68
|
1245 theme_source
|
jpayne@68
|
1246 builtin_name
|
jpayne@68
|
1247 custom_name
|
jpayne@68
|
1248
|
jpayne@68
|
1249 Attributes updated:
|
jpayne@68
|
1250 highlight_sample: Set the tag elements to the theme.
|
jpayne@68
|
1251
|
jpayne@68
|
1252 Methods:
|
jpayne@68
|
1253 set_color_sample
|
jpayne@68
|
1254
|
jpayne@68
|
1255 Called from:
|
jpayne@68
|
1256 var_changed_builtin_name
|
jpayne@68
|
1257 var_changed_custom_name
|
jpayne@68
|
1258 load_theme_cfg
|
jpayne@68
|
1259 """
|
jpayne@68
|
1260 if self.theme_source.get(): # Default theme
|
jpayne@68
|
1261 theme = self.builtin_name.get()
|
jpayne@68
|
1262 else: # User theme
|
jpayne@68
|
1263 theme = self.custom_name.get()
|
jpayne@68
|
1264 for element_title in self.theme_elements:
|
jpayne@68
|
1265 element = self.theme_elements[element_title][0]
|
jpayne@68
|
1266 colors = idleConf.GetHighlight(theme, element)
|
jpayne@68
|
1267 if element == 'cursor': # Cursor sample needs special painting.
|
jpayne@68
|
1268 colors['background'] = idleConf.GetHighlight(
|
jpayne@68
|
1269 theme, 'normal')['background']
|
jpayne@68
|
1270 # Handle any unsaved changes to this theme.
|
jpayne@68
|
1271 if theme in changes['highlight']:
|
jpayne@68
|
1272 theme_dict = changes['highlight'][theme]
|
jpayne@68
|
1273 if element + '-foreground' in theme_dict:
|
jpayne@68
|
1274 colors['foreground'] = theme_dict[element + '-foreground']
|
jpayne@68
|
1275 if element + '-background' in theme_dict:
|
jpayne@68
|
1276 colors['background'] = theme_dict[element + '-background']
|
jpayne@68
|
1277 self.highlight_sample.tag_config(element, **colors)
|
jpayne@68
|
1278 self.set_color_sample()
|
jpayne@68
|
1279
|
jpayne@68
|
1280 def save_new(self, theme_name, theme):
|
jpayne@68
|
1281 """Save a newly created theme to idleConf.
|
jpayne@68
|
1282
|
jpayne@68
|
1283 theme_name - string, the name of the new theme
|
jpayne@68
|
1284 theme - dictionary containing the new theme
|
jpayne@68
|
1285 """
|
jpayne@68
|
1286 if not idleConf.userCfg['highlight'].has_section(theme_name):
|
jpayne@68
|
1287 idleConf.userCfg['highlight'].add_section(theme_name)
|
jpayne@68
|
1288 for element in theme:
|
jpayne@68
|
1289 value = theme[element]
|
jpayne@68
|
1290 idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
|
jpayne@68
|
1291
|
jpayne@68
|
1292 def askyesno(self, *args, **kwargs):
|
jpayne@68
|
1293 # Make testing easier. Could change implementation.
|
jpayne@68
|
1294 return messagebox.askyesno(*args, **kwargs)
|
jpayne@68
|
1295
|
jpayne@68
|
1296 def delete_custom(self):
|
jpayne@68
|
1297 """Handle event to delete custom theme.
|
jpayne@68
|
1298
|
jpayne@68
|
1299 The current theme is deactivated and the default theme is
|
jpayne@68
|
1300 activated. The custom theme is permanently removed from
|
jpayne@68
|
1301 the config file.
|
jpayne@68
|
1302
|
jpayne@68
|
1303 Attributes accessed:
|
jpayne@68
|
1304 custom_name
|
jpayne@68
|
1305
|
jpayne@68
|
1306 Attributes updated:
|
jpayne@68
|
1307 custom_theme_on
|
jpayne@68
|
1308 customlist
|
jpayne@68
|
1309 theme_source
|
jpayne@68
|
1310 builtin_name
|
jpayne@68
|
1311
|
jpayne@68
|
1312 Methods:
|
jpayne@68
|
1313 deactivate_current_config
|
jpayne@68
|
1314 save_all_changed_extensions
|
jpayne@68
|
1315 activate_config_changes
|
jpayne@68
|
1316 set_theme_type
|
jpayne@68
|
1317 """
|
jpayne@68
|
1318 theme_name = self.custom_name.get()
|
jpayne@68
|
1319 delmsg = 'Are you sure you wish to delete the theme %r ?'
|
jpayne@68
|
1320 if not self.askyesno(
|
jpayne@68
|
1321 'Delete Theme', delmsg % theme_name, parent=self):
|
jpayne@68
|
1322 return
|
jpayne@68
|
1323 self.cd.deactivate_current_config()
|
jpayne@68
|
1324 # Remove theme from changes, config, and file.
|
jpayne@68
|
1325 changes.delete_section('highlight', theme_name)
|
jpayne@68
|
1326 # Reload user theme list.
|
jpayne@68
|
1327 item_list = idleConf.GetSectionList('user', 'highlight')
|
jpayne@68
|
1328 item_list.sort()
|
jpayne@68
|
1329 if not item_list:
|
jpayne@68
|
1330 self.custom_theme_on.state(('disabled',))
|
jpayne@68
|
1331 self.customlist.SetMenu(item_list, '- no custom themes -')
|
jpayne@68
|
1332 else:
|
jpayne@68
|
1333 self.customlist.SetMenu(item_list, item_list[0])
|
jpayne@68
|
1334 # Revert to default theme.
|
jpayne@68
|
1335 self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
|
jpayne@68
|
1336 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
|
jpayne@68
|
1337 # User can't back out of these changes, they must be applied now.
|
jpayne@68
|
1338 changes.save_all()
|
jpayne@68
|
1339 self.cd.save_all_changed_extensions()
|
jpayne@68
|
1340 self.cd.activate_config_changes()
|
jpayne@68
|
1341 self.set_theme_type()
|
jpayne@68
|
1342
|
jpayne@68
|
1343
|
jpayne@68
|
1344 class KeysPage(Frame):
|
jpayne@68
|
1345
|
jpayne@68
|
1346 def __init__(self, master):
|
jpayne@68
|
1347 super().__init__(master)
|
jpayne@68
|
1348 self.cd = master.master
|
jpayne@68
|
1349 self.create_page_keys()
|
jpayne@68
|
1350 self.load_key_cfg()
|
jpayne@68
|
1351
|
jpayne@68
|
1352 def create_page_keys(self):
|
jpayne@68
|
1353 """Return frame of widgets for Keys tab.
|
jpayne@68
|
1354
|
jpayne@68
|
1355 Enable users to provisionally change both individual and sets of
|
jpayne@68
|
1356 keybindings (shortcut keys). Except for features implemented as
|
jpayne@68
|
1357 extensions, keybindings are stored in complete sets called
|
jpayne@68
|
1358 keysets. Built-in keysets in idlelib/config-keys.def are fixed
|
jpayne@68
|
1359 as far as the dialog is concerned. Any keyset can be used as the
|
jpayne@68
|
1360 base for a new custom keyset, stored in .idlerc/config-keys.cfg.
|
jpayne@68
|
1361
|
jpayne@68
|
1362 Function load_key_cfg() initializes tk variables and keyset
|
jpayne@68
|
1363 lists and calls load_keys_list for the current keyset.
|
jpayne@68
|
1364 Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
|
jpayne@68
|
1365 keyset_source, which controls if the current set of keybindings
|
jpayne@68
|
1366 are from a builtin or custom keyset. DynOptionMenus builtinlist
|
jpayne@68
|
1367 and customlist contain lists of the builtin and custom keysets,
|
jpayne@68
|
1368 respectively, and the current item from each list is stored in
|
jpayne@68
|
1369 vars builtin_name and custom_name.
|
jpayne@68
|
1370
|
jpayne@68
|
1371 Button delete_custom_keys invokes delete_custom_keys() to delete
|
jpayne@68
|
1372 a custom keyset from idleConf.userCfg['keys'] and changes. Button
|
jpayne@68
|
1373 save_custom_keys invokes save_as_new_key_set() which calls
|
jpayne@68
|
1374 get_new_keys_name() and create_new_key_set() to save a custom keyset
|
jpayne@68
|
1375 and its keybindings to idleConf.userCfg['keys'].
|
jpayne@68
|
1376
|
jpayne@68
|
1377 Listbox bindingslist contains all of the keybindings for the
|
jpayne@68
|
1378 selected keyset. The keybindings are loaded in load_keys_list()
|
jpayne@68
|
1379 and are pairs of (event, [keys]) where keys can be a list
|
jpayne@68
|
1380 of one or more key combinations to bind to the same event.
|
jpayne@68
|
1381 Mouse button 1 click invokes on_bindingslist_select(), which
|
jpayne@68
|
1382 allows button_new_keys to be clicked.
|
jpayne@68
|
1383
|
jpayne@68
|
1384 So, an item is selected in listbindings, which activates
|
jpayne@68
|
1385 button_new_keys, and clicking button_new_keys calls function
|
jpayne@68
|
1386 get_new_keys(). Function get_new_keys() gets the key mappings from the
|
jpayne@68
|
1387 current keyset for the binding event item that was selected. The
|
jpayne@68
|
1388 function then displays another dialog, GetKeysDialog, with the
|
jpayne@68
|
1389 selected binding event and current keys and allows new key sequences
|
jpayne@68
|
1390 to be entered for that binding event. If the keys aren't
|
jpayne@68
|
1391 changed, nothing happens. If the keys are changed and the keyset
|
jpayne@68
|
1392 is a builtin, function get_new_keys_name() will be called
|
jpayne@68
|
1393 for input of a custom keyset name. If no name is given, then the
|
jpayne@68
|
1394 change to the keybinding will abort and no updates will be made. If
|
jpayne@68
|
1395 a custom name is entered in the prompt or if the current keyset was
|
jpayne@68
|
1396 already custom (and thus didn't require a prompt), then
|
jpayne@68
|
1397 idleConf.userCfg['keys'] is updated in function create_new_key_set()
|
jpayne@68
|
1398 with the change to the event binding. The item listing in bindingslist
|
jpayne@68
|
1399 is updated with the new keys. Var keybinding is also set which invokes
|
jpayne@68
|
1400 the callback function, var_changed_keybinding, to add the change to
|
jpayne@68
|
1401 the 'keys' or 'extensions' changes tracker based on the binding type.
|
jpayne@68
|
1402
|
jpayne@68
|
1403 Tk Variables:
|
jpayne@68
|
1404 keybinding: Action/key bindings.
|
jpayne@68
|
1405
|
jpayne@68
|
1406 Methods:
|
jpayne@68
|
1407 load_keys_list: Reload active set.
|
jpayne@68
|
1408 create_new_key_set: Combine active keyset and changes.
|
jpayne@68
|
1409 set_keys_type: Command for keyset_source.
|
jpayne@68
|
1410 save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
|
jpayne@68
|
1411 deactivate_current_config: Remove keys bindings in editors.
|
jpayne@68
|
1412
|
jpayne@68
|
1413 Widgets for KeysPage(frame): (*) widgets bound to self
|
jpayne@68
|
1414 frame_key_sets: LabelFrame
|
jpayne@68
|
1415 frames[0]: Frame
|
jpayne@68
|
1416 (*)builtin_keyset_on: Radiobutton - var keyset_source
|
jpayne@68
|
1417 (*)custom_keyset_on: Radiobutton - var keyset_source
|
jpayne@68
|
1418 (*)builtinlist: DynOptionMenu - var builtin_name,
|
jpayne@68
|
1419 func keybinding_selected
|
jpayne@68
|
1420 (*)customlist: DynOptionMenu - var custom_name,
|
jpayne@68
|
1421 func keybinding_selected
|
jpayne@68
|
1422 (*)keys_message: Label
|
jpayne@68
|
1423 frames[1]: Frame
|
jpayne@68
|
1424 (*)button_delete_custom_keys: Button - delete_custom_keys
|
jpayne@68
|
1425 (*)button_save_custom_keys: Button - save_as_new_key_set
|
jpayne@68
|
1426 frame_custom: LabelFrame
|
jpayne@68
|
1427 frame_target: Frame
|
jpayne@68
|
1428 target_title: Label
|
jpayne@68
|
1429 scroll_target_y: Scrollbar
|
jpayne@68
|
1430 scroll_target_x: Scrollbar
|
jpayne@68
|
1431 (*)bindingslist: ListBox - on_bindingslist_select
|
jpayne@68
|
1432 (*)button_new_keys: Button - get_new_keys & ..._name
|
jpayne@68
|
1433 """
|
jpayne@68
|
1434 self.builtin_name = tracers.add(
|
jpayne@68
|
1435 StringVar(self), self.var_changed_builtin_name)
|
jpayne@68
|
1436 self.custom_name = tracers.add(
|
jpayne@68
|
1437 StringVar(self), self.var_changed_custom_name)
|
jpayne@68
|
1438 self.keyset_source = tracers.add(
|
jpayne@68
|
1439 BooleanVar(self), self.var_changed_keyset_source)
|
jpayne@68
|
1440 self.keybinding = tracers.add(
|
jpayne@68
|
1441 StringVar(self), self.var_changed_keybinding)
|
jpayne@68
|
1442
|
jpayne@68
|
1443 # Create widgets:
|
jpayne@68
|
1444 # body and section frames.
|
jpayne@68
|
1445 frame_custom = LabelFrame(
|
jpayne@68
|
1446 self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
1447 text=' Custom Key Bindings ')
|
jpayne@68
|
1448 frame_key_sets = LabelFrame(
|
jpayne@68
|
1449 self, borderwidth=2, relief=GROOVE, text=' Key Set ')
|
jpayne@68
|
1450 # frame_custom.
|
jpayne@68
|
1451 frame_target = Frame(frame_custom)
|
jpayne@68
|
1452 target_title = Label(frame_target, text='Action - Key(s)')
|
jpayne@68
|
1453 scroll_target_y = Scrollbar(frame_target)
|
jpayne@68
|
1454 scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
|
jpayne@68
|
1455 self.bindingslist = Listbox(
|
jpayne@68
|
1456 frame_target, takefocus=FALSE, exportselection=FALSE)
|
jpayne@68
|
1457 self.bindingslist.bind('<ButtonRelease-1>',
|
jpayne@68
|
1458 self.on_bindingslist_select)
|
jpayne@68
|
1459 scroll_target_y['command'] = self.bindingslist.yview
|
jpayne@68
|
1460 scroll_target_x['command'] = self.bindingslist.xview
|
jpayne@68
|
1461 self.bindingslist['yscrollcommand'] = scroll_target_y.set
|
jpayne@68
|
1462 self.bindingslist['xscrollcommand'] = scroll_target_x.set
|
jpayne@68
|
1463 self.button_new_keys = Button(
|
jpayne@68
|
1464 frame_custom, text='Get New Keys for Selection',
|
jpayne@68
|
1465 command=self.get_new_keys, state='disabled')
|
jpayne@68
|
1466 # frame_key_sets.
|
jpayne@68
|
1467 frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
|
jpayne@68
|
1468 for i in range(2)]
|
jpayne@68
|
1469 self.builtin_keyset_on = Radiobutton(
|
jpayne@68
|
1470 frames[0], variable=self.keyset_source, value=1,
|
jpayne@68
|
1471 command=self.set_keys_type, text='Use a Built-in Key Set')
|
jpayne@68
|
1472 self.custom_keyset_on = Radiobutton(
|
jpayne@68
|
1473 frames[0], variable=self.keyset_source, value=0,
|
jpayne@68
|
1474 command=self.set_keys_type, text='Use a Custom Key Set')
|
jpayne@68
|
1475 self.builtinlist = DynOptionMenu(
|
jpayne@68
|
1476 frames[0], self.builtin_name, None, command=None)
|
jpayne@68
|
1477 self.customlist = DynOptionMenu(
|
jpayne@68
|
1478 frames[0], self.custom_name, None, command=None)
|
jpayne@68
|
1479 self.button_delete_custom_keys = Button(
|
jpayne@68
|
1480 frames[1], text='Delete Custom Key Set',
|
jpayne@68
|
1481 command=self.delete_custom_keys)
|
jpayne@68
|
1482 self.button_save_custom_keys = Button(
|
jpayne@68
|
1483 frames[1], text='Save as New Custom Key Set',
|
jpayne@68
|
1484 command=self.save_as_new_key_set)
|
jpayne@68
|
1485 self.keys_message = Label(frames[0], borderwidth=2)
|
jpayne@68
|
1486
|
jpayne@68
|
1487 # Pack widgets:
|
jpayne@68
|
1488 # body.
|
jpayne@68
|
1489 frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
1490 frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
|
jpayne@68
|
1491 # frame_custom.
|
jpayne@68
|
1492 self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
|
jpayne@68
|
1493 frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
1494 # frame_target.
|
jpayne@68
|
1495 frame_target.columnconfigure(0, weight=1)
|
jpayne@68
|
1496 frame_target.rowconfigure(1, weight=1)
|
jpayne@68
|
1497 target_title.grid(row=0, column=0, columnspan=2, sticky=W)
|
jpayne@68
|
1498 self.bindingslist.grid(row=1, column=0, sticky=NSEW)
|
jpayne@68
|
1499 scroll_target_y.grid(row=1, column=1, sticky=NS)
|
jpayne@68
|
1500 scroll_target_x.grid(row=2, column=0, sticky=EW)
|
jpayne@68
|
1501 # frame_key_sets.
|
jpayne@68
|
1502 self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
|
jpayne@68
|
1503 self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
|
jpayne@68
|
1504 self.builtinlist.grid(row=0, column=1, sticky=NSEW)
|
jpayne@68
|
1505 self.customlist.grid(row=1, column=1, sticky=NSEW)
|
jpayne@68
|
1506 self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
|
jpayne@68
|
1507 self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
|
jpayne@68
|
1508 self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
|
jpayne@68
|
1509 frames[0].pack(side=TOP, fill=BOTH, expand=True)
|
jpayne@68
|
1510 frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
|
jpayne@68
|
1511
|
jpayne@68
|
1512 def load_key_cfg(self):
|
jpayne@68
|
1513 "Load current configuration settings for the keybinding options."
|
jpayne@68
|
1514 # Set current keys type radiobutton.
|
jpayne@68
|
1515 self.keyset_source.set(idleConf.GetOption(
|
jpayne@68
|
1516 'main', 'Keys', 'default', type='bool', default=1))
|
jpayne@68
|
1517 # Set current keys.
|
jpayne@68
|
1518 current_option = idleConf.CurrentKeys()
|
jpayne@68
|
1519 # Load available keyset option menus.
|
jpayne@68
|
1520 if self.keyset_source.get(): # Default theme selected.
|
jpayne@68
|
1521 item_list = idleConf.GetSectionList('default', 'keys')
|
jpayne@68
|
1522 item_list.sort()
|
jpayne@68
|
1523 self.builtinlist.SetMenu(item_list, current_option)
|
jpayne@68
|
1524 item_list = idleConf.GetSectionList('user', 'keys')
|
jpayne@68
|
1525 item_list.sort()
|
jpayne@68
|
1526 if not item_list:
|
jpayne@68
|
1527 self.custom_keyset_on.state(('disabled',))
|
jpayne@68
|
1528 self.custom_name.set('- no custom keys -')
|
jpayne@68
|
1529 else:
|
jpayne@68
|
1530 self.customlist.SetMenu(item_list, item_list[0])
|
jpayne@68
|
1531 else: # User key set selected.
|
jpayne@68
|
1532 item_list = idleConf.GetSectionList('user', 'keys')
|
jpayne@68
|
1533 item_list.sort()
|
jpayne@68
|
1534 self.customlist.SetMenu(item_list, current_option)
|
jpayne@68
|
1535 item_list = idleConf.GetSectionList('default', 'keys')
|
jpayne@68
|
1536 item_list.sort()
|
jpayne@68
|
1537 self.builtinlist.SetMenu(item_list, idleConf.default_keys())
|
jpayne@68
|
1538 self.set_keys_type()
|
jpayne@68
|
1539 # Load keyset element list.
|
jpayne@68
|
1540 keyset_name = idleConf.CurrentKeys()
|
jpayne@68
|
1541 self.load_keys_list(keyset_name)
|
jpayne@68
|
1542
|
jpayne@68
|
1543 def var_changed_builtin_name(self, *params):
|
jpayne@68
|
1544 "Process selection of builtin key set."
|
jpayne@68
|
1545 old_keys = (
|
jpayne@68
|
1546 'IDLE Classic Windows',
|
jpayne@68
|
1547 'IDLE Classic Unix',
|
jpayne@68
|
1548 'IDLE Classic Mac',
|
jpayne@68
|
1549 'IDLE Classic OSX',
|
jpayne@68
|
1550 )
|
jpayne@68
|
1551 value = self.builtin_name.get()
|
jpayne@68
|
1552 if value not in old_keys:
|
jpayne@68
|
1553 if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
|
jpayne@68
|
1554 changes.add_option('main', 'Keys', 'name', old_keys[0])
|
jpayne@68
|
1555 changes.add_option('main', 'Keys', 'name2', value)
|
jpayne@68
|
1556 self.keys_message['text'] = 'New key set, see Help'
|
jpayne@68
|
1557 else:
|
jpayne@68
|
1558 changes.add_option('main', 'Keys', 'name', value)
|
jpayne@68
|
1559 changes.add_option('main', 'Keys', 'name2', '')
|
jpayne@68
|
1560 self.keys_message['text'] = ''
|
jpayne@68
|
1561 self.load_keys_list(value)
|
jpayne@68
|
1562
|
jpayne@68
|
1563 def var_changed_custom_name(self, *params):
|
jpayne@68
|
1564 "Process selection of custom key set."
|
jpayne@68
|
1565 value = self.custom_name.get()
|
jpayne@68
|
1566 if value != '- no custom keys -':
|
jpayne@68
|
1567 changes.add_option('main', 'Keys', 'name', value)
|
jpayne@68
|
1568 self.load_keys_list(value)
|
jpayne@68
|
1569
|
jpayne@68
|
1570 def var_changed_keyset_source(self, *params):
|
jpayne@68
|
1571 "Process toggle between builtin key set and custom key set."
|
jpayne@68
|
1572 value = self.keyset_source.get()
|
jpayne@68
|
1573 changes.add_option('main', 'Keys', 'default', value)
|
jpayne@68
|
1574 if value:
|
jpayne@68
|
1575 self.var_changed_builtin_name()
|
jpayne@68
|
1576 else:
|
jpayne@68
|
1577 self.var_changed_custom_name()
|
jpayne@68
|
1578
|
jpayne@68
|
1579 def var_changed_keybinding(self, *params):
|
jpayne@68
|
1580 "Store change to a keybinding."
|
jpayne@68
|
1581 value = self.keybinding.get()
|
jpayne@68
|
1582 key_set = self.custom_name.get()
|
jpayne@68
|
1583 event = self.bindingslist.get(ANCHOR).split()[0]
|
jpayne@68
|
1584 if idleConf.IsCoreBinding(event):
|
jpayne@68
|
1585 changes.add_option('keys', key_set, event, value)
|
jpayne@68
|
1586 else: # Event is an extension binding.
|
jpayne@68
|
1587 ext_name = idleConf.GetExtnNameForEvent(event)
|
jpayne@68
|
1588 ext_keybind_section = ext_name + '_cfgBindings'
|
jpayne@68
|
1589 changes.add_option('extensions', ext_keybind_section, event, value)
|
jpayne@68
|
1590
|
jpayne@68
|
1591 def set_keys_type(self):
|
jpayne@68
|
1592 "Set available screen options based on builtin or custom key set."
|
jpayne@68
|
1593 if self.keyset_source.get():
|
jpayne@68
|
1594 self.builtinlist['state'] = 'normal'
|
jpayne@68
|
1595 self.customlist['state'] = 'disabled'
|
jpayne@68
|
1596 self.button_delete_custom_keys.state(('disabled',))
|
jpayne@68
|
1597 else:
|
jpayne@68
|
1598 self.builtinlist['state'] = 'disabled'
|
jpayne@68
|
1599 self.custom_keyset_on.state(('!disabled',))
|
jpayne@68
|
1600 self.customlist['state'] = 'normal'
|
jpayne@68
|
1601 self.button_delete_custom_keys.state(('!disabled',))
|
jpayne@68
|
1602
|
jpayne@68
|
1603 def get_new_keys(self):
|
jpayne@68
|
1604 """Handle event to change key binding for selected line.
|
jpayne@68
|
1605
|
jpayne@68
|
1606 A selection of a key/binding in the list of current
|
jpayne@68
|
1607 bindings pops up a dialog to enter a new binding. If
|
jpayne@68
|
1608 the current key set is builtin and a binding has
|
jpayne@68
|
1609 changed, then a name for a custom key set needs to be
|
jpayne@68
|
1610 entered for the change to be applied.
|
jpayne@68
|
1611 """
|
jpayne@68
|
1612 list_index = self.bindingslist.index(ANCHOR)
|
jpayne@68
|
1613 binding = self.bindingslist.get(list_index)
|
jpayne@68
|
1614 bind_name = binding.split()[0]
|
jpayne@68
|
1615 if self.keyset_source.get():
|
jpayne@68
|
1616 current_key_set_name = self.builtin_name.get()
|
jpayne@68
|
1617 else:
|
jpayne@68
|
1618 current_key_set_name = self.custom_name.get()
|
jpayne@68
|
1619 current_bindings = idleConf.GetCurrentKeySet()
|
jpayne@68
|
1620 if current_key_set_name in changes['keys']: # unsaved changes
|
jpayne@68
|
1621 key_set_changes = changes['keys'][current_key_set_name]
|
jpayne@68
|
1622 for event in key_set_changes:
|
jpayne@68
|
1623 current_bindings[event] = key_set_changes[event].split()
|
jpayne@68
|
1624 current_key_sequences = list(current_bindings.values())
|
jpayne@68
|
1625 new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
|
jpayne@68
|
1626 current_key_sequences).result
|
jpayne@68
|
1627 if new_keys:
|
jpayne@68
|
1628 if self.keyset_source.get(): # Current key set is a built-in.
|
jpayne@68
|
1629 message = ('Your changes will be saved as a new Custom Key Set.'
|
jpayne@68
|
1630 ' Enter a name for your new Custom Key Set below.')
|
jpayne@68
|
1631 new_keyset = self.get_new_keys_name(message)
|
jpayne@68
|
1632 if not new_keyset: # User cancelled custom key set creation.
|
jpayne@68
|
1633 self.bindingslist.select_set(list_index)
|
jpayne@68
|
1634 self.bindingslist.select_anchor(list_index)
|
jpayne@68
|
1635 return
|
jpayne@68
|
1636 else: # Create new custom key set based on previously active key set.
|
jpayne@68
|
1637 self.create_new_key_set(new_keyset)
|
jpayne@68
|
1638 self.bindingslist.delete(list_index)
|
jpayne@68
|
1639 self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
|
jpayne@68
|
1640 self.bindingslist.select_set(list_index)
|
jpayne@68
|
1641 self.bindingslist.select_anchor(list_index)
|
jpayne@68
|
1642 self.keybinding.set(new_keys)
|
jpayne@68
|
1643 else:
|
jpayne@68
|
1644 self.bindingslist.select_set(list_index)
|
jpayne@68
|
1645 self.bindingslist.select_anchor(list_index)
|
jpayne@68
|
1646
|
jpayne@68
|
1647 def get_new_keys_name(self, message):
|
jpayne@68
|
1648 "Return new key set name from query popup."
|
jpayne@68
|
1649 used_names = (idleConf.GetSectionList('user', 'keys') +
|
jpayne@68
|
1650 idleConf.GetSectionList('default', 'keys'))
|
jpayne@68
|
1651 new_keyset = SectionName(
|
jpayne@68
|
1652 self, 'New Custom Key Set', message, used_names).result
|
jpayne@68
|
1653 return new_keyset
|
jpayne@68
|
1654
|
jpayne@68
|
1655 def save_as_new_key_set(self):
|
jpayne@68
|
1656 "Prompt for name of new key set and save changes using that name."
|
jpayne@68
|
1657 new_keys_name = self.get_new_keys_name('New Key Set Name:')
|
jpayne@68
|
1658 if new_keys_name:
|
jpayne@68
|
1659 self.create_new_key_set(new_keys_name)
|
jpayne@68
|
1660
|
jpayne@68
|
1661 def on_bindingslist_select(self, event):
|
jpayne@68
|
1662 "Activate button to assign new keys to selected action."
|
jpayne@68
|
1663 self.button_new_keys.state(('!disabled',))
|
jpayne@68
|
1664
|
jpayne@68
|
1665 def create_new_key_set(self, new_key_set_name):
|
jpayne@68
|
1666 """Create a new custom key set with the given name.
|
jpayne@68
|
1667
|
jpayne@68
|
1668 Copy the bindings/keys from the previously active keyset
|
jpayne@68
|
1669 to the new keyset and activate the new custom keyset.
|
jpayne@68
|
1670 """
|
jpayne@68
|
1671 if self.keyset_source.get():
|
jpayne@68
|
1672 prev_key_set_name = self.builtin_name.get()
|
jpayne@68
|
1673 else:
|
jpayne@68
|
1674 prev_key_set_name = self.custom_name.get()
|
jpayne@68
|
1675 prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
|
jpayne@68
|
1676 new_keys = {}
|
jpayne@68
|
1677 for event in prev_keys: # Add key set to changed items.
|
jpayne@68
|
1678 event_name = event[2:-2] # Trim off the angle brackets.
|
jpayne@68
|
1679 binding = ' '.join(prev_keys[event])
|
jpayne@68
|
1680 new_keys[event_name] = binding
|
jpayne@68
|
1681 # Handle any unsaved changes to prev key set.
|
jpayne@68
|
1682 if prev_key_set_name in changes['keys']:
|
jpayne@68
|
1683 key_set_changes = changes['keys'][prev_key_set_name]
|
jpayne@68
|
1684 for event in key_set_changes:
|
jpayne@68
|
1685 new_keys[event] = key_set_changes[event]
|
jpayne@68
|
1686 # Save the new key set.
|
jpayne@68
|
1687 self.save_new_key_set(new_key_set_name, new_keys)
|
jpayne@68
|
1688 # Change GUI over to the new key set.
|
jpayne@68
|
1689 custom_key_list = idleConf.GetSectionList('user', 'keys')
|
jpayne@68
|
1690 custom_key_list.sort()
|
jpayne@68
|
1691 self.customlist.SetMenu(custom_key_list, new_key_set_name)
|
jpayne@68
|
1692 self.keyset_source.set(0)
|
jpayne@68
|
1693 self.set_keys_type()
|
jpayne@68
|
1694
|
jpayne@68
|
1695 def load_keys_list(self, keyset_name):
|
jpayne@68
|
1696 """Reload the list of action/key binding pairs for the active key set.
|
jpayne@68
|
1697
|
jpayne@68
|
1698 An action/key binding can be selected to change the key binding.
|
jpayne@68
|
1699 """
|
jpayne@68
|
1700 reselect = False
|
jpayne@68
|
1701 if self.bindingslist.curselection():
|
jpayne@68
|
1702 reselect = True
|
jpayne@68
|
1703 list_index = self.bindingslist.index(ANCHOR)
|
jpayne@68
|
1704 keyset = idleConf.GetKeySet(keyset_name)
|
jpayne@68
|
1705 bind_names = list(keyset.keys())
|
jpayne@68
|
1706 bind_names.sort()
|
jpayne@68
|
1707 self.bindingslist.delete(0, END)
|
jpayne@68
|
1708 for bind_name in bind_names:
|
jpayne@68
|
1709 key = ' '.join(keyset[bind_name])
|
jpayne@68
|
1710 bind_name = bind_name[2:-2] # Trim off the angle brackets.
|
jpayne@68
|
1711 if keyset_name in changes['keys']:
|
jpayne@68
|
1712 # Handle any unsaved changes to this key set.
|
jpayne@68
|
1713 if bind_name in changes['keys'][keyset_name]:
|
jpayne@68
|
1714 key = changes['keys'][keyset_name][bind_name]
|
jpayne@68
|
1715 self.bindingslist.insert(END, bind_name+' - '+key)
|
jpayne@68
|
1716 if reselect:
|
jpayne@68
|
1717 self.bindingslist.see(list_index)
|
jpayne@68
|
1718 self.bindingslist.select_set(list_index)
|
jpayne@68
|
1719 self.bindingslist.select_anchor(list_index)
|
jpayne@68
|
1720
|
jpayne@68
|
1721 @staticmethod
|
jpayne@68
|
1722 def save_new_key_set(keyset_name, keyset):
|
jpayne@68
|
1723 """Save a newly created core key set.
|
jpayne@68
|
1724
|
jpayne@68
|
1725 Add keyset to idleConf.userCfg['keys'], not to disk.
|
jpayne@68
|
1726 If the keyset doesn't exist, it is created. The
|
jpayne@68
|
1727 binding/keys are taken from the keyset argument.
|
jpayne@68
|
1728
|
jpayne@68
|
1729 keyset_name - string, the name of the new key set
|
jpayne@68
|
1730 keyset - dictionary containing the new keybindings
|
jpayne@68
|
1731 """
|
jpayne@68
|
1732 if not idleConf.userCfg['keys'].has_section(keyset_name):
|
jpayne@68
|
1733 idleConf.userCfg['keys'].add_section(keyset_name)
|
jpayne@68
|
1734 for event in keyset:
|
jpayne@68
|
1735 value = keyset[event]
|
jpayne@68
|
1736 idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
|
jpayne@68
|
1737
|
jpayne@68
|
1738 def askyesno(self, *args, **kwargs):
|
jpayne@68
|
1739 # Make testing easier. Could change implementation.
|
jpayne@68
|
1740 return messagebox.askyesno(*args, **kwargs)
|
jpayne@68
|
1741
|
jpayne@68
|
1742 def delete_custom_keys(self):
|
jpayne@68
|
1743 """Handle event to delete a custom key set.
|
jpayne@68
|
1744
|
jpayne@68
|
1745 Applying the delete deactivates the current configuration and
|
jpayne@68
|
1746 reverts to the default. The custom key set is permanently
|
jpayne@68
|
1747 deleted from the config file.
|
jpayne@68
|
1748 """
|
jpayne@68
|
1749 keyset_name = self.custom_name.get()
|
jpayne@68
|
1750 delmsg = 'Are you sure you wish to delete the key set %r ?'
|
jpayne@68
|
1751 if not self.askyesno(
|
jpayne@68
|
1752 'Delete Key Set', delmsg % keyset_name, parent=self):
|
jpayne@68
|
1753 return
|
jpayne@68
|
1754 self.cd.deactivate_current_config()
|
jpayne@68
|
1755 # Remove key set from changes, config, and file.
|
jpayne@68
|
1756 changes.delete_section('keys', keyset_name)
|
jpayne@68
|
1757 # Reload user key set list.
|
jpayne@68
|
1758 item_list = idleConf.GetSectionList('user', 'keys')
|
jpayne@68
|
1759 item_list.sort()
|
jpayne@68
|
1760 if not item_list:
|
jpayne@68
|
1761 self.custom_keyset_on.state(('disabled',))
|
jpayne@68
|
1762 self.customlist.SetMenu(item_list, '- no custom keys -')
|
jpayne@68
|
1763 else:
|
jpayne@68
|
1764 self.customlist.SetMenu(item_list, item_list[0])
|
jpayne@68
|
1765 # Revert to default key set.
|
jpayne@68
|
1766 self.keyset_source.set(idleConf.defaultCfg['main']
|
jpayne@68
|
1767 .Get('Keys', 'default'))
|
jpayne@68
|
1768 self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
|
jpayne@68
|
1769 or idleConf.default_keys())
|
jpayne@68
|
1770 # User can't back out of these changes, they must be applied now.
|
jpayne@68
|
1771 changes.save_all()
|
jpayne@68
|
1772 self.cd.save_all_changed_extensions()
|
jpayne@68
|
1773 self.cd.activate_config_changes()
|
jpayne@68
|
1774 self.set_keys_type()
|
jpayne@68
|
1775
|
jpayne@68
|
1776
|
jpayne@68
|
1777 class GenPage(Frame):
|
jpayne@68
|
1778
|
jpayne@68
|
1779 def __init__(self, master):
|
jpayne@68
|
1780 super().__init__(master)
|
jpayne@68
|
1781
|
jpayne@68
|
1782 self.init_validators()
|
jpayne@68
|
1783 self.create_page_general()
|
jpayne@68
|
1784 self.load_general_cfg()
|
jpayne@68
|
1785
|
jpayne@68
|
1786 def init_validators(self):
|
jpayne@68
|
1787 digits_or_empty_re = re.compile(r'[0-9]*')
|
jpayne@68
|
1788 def is_digits_or_empty(s):
|
jpayne@68
|
1789 "Return 's is blank or contains only digits'"
|
jpayne@68
|
1790 return digits_or_empty_re.fullmatch(s) is not None
|
jpayne@68
|
1791 self.digits_only = (self.register(is_digits_or_empty), '%P',)
|
jpayne@68
|
1792
|
jpayne@68
|
1793 def create_page_general(self):
|
jpayne@68
|
1794 """Return frame of widgets for General tab.
|
jpayne@68
|
1795
|
jpayne@68
|
1796 Enable users to provisionally change general options. Function
|
jpayne@68
|
1797 load_general_cfg initializes tk variables and helplist using
|
jpayne@68
|
1798 idleConf. Radiobuttons startup_shell_on and startup_editor_on
|
jpayne@68
|
1799 set var startup_edit. Radiobuttons save_ask_on and save_auto_on
|
jpayne@68
|
1800 set var autosave. Entry boxes win_width_int and win_height_int
|
jpayne@68
|
1801 set var win_width and win_height. Setting var_name invokes the
|
jpayne@68
|
1802 default callback that adds option to changes.
|
jpayne@68
|
1803
|
jpayne@68
|
1804 Helplist: load_general_cfg loads list user_helplist with
|
jpayne@68
|
1805 name, position pairs and copies names to listbox helplist.
|
jpayne@68
|
1806 Clicking a name invokes help_source selected. Clicking
|
jpayne@68
|
1807 button_helplist_name invokes helplist_item_name, which also
|
jpayne@68
|
1808 changes user_helplist. These functions all call
|
jpayne@68
|
1809 set_add_delete_state. All but load call update_help_changes to
|
jpayne@68
|
1810 rewrite changes['main']['HelpFiles'].
|
jpayne@68
|
1811
|
jpayne@68
|
1812 Widgets for GenPage(Frame): (*) widgets bound to self
|
jpayne@68
|
1813 frame_window: LabelFrame
|
jpayne@68
|
1814 frame_run: Frame
|
jpayne@68
|
1815 startup_title: Label
|
jpayne@68
|
1816 (*)startup_editor_on: Radiobutton - startup_edit
|
jpayne@68
|
1817 (*)startup_shell_on: Radiobutton - startup_edit
|
jpayne@68
|
1818 frame_win_size: Frame
|
jpayne@68
|
1819 win_size_title: Label
|
jpayne@68
|
1820 win_width_title: Label
|
jpayne@68
|
1821 (*)win_width_int: Entry - win_width
|
jpayne@68
|
1822 win_height_title: Label
|
jpayne@68
|
1823 (*)win_height_int: Entry - win_height
|
jpayne@68
|
1824 frame_cursor_blink: Frame
|
jpayne@68
|
1825 cursor_blink_title: Label
|
jpayne@68
|
1826 (*)cursor_blink_bool: Checkbutton - cursor_blink
|
jpayne@68
|
1827 frame_autocomplete: Frame
|
jpayne@68
|
1828 auto_wait_title: Label
|
jpayne@68
|
1829 (*)auto_wait_int: Entry - autocomplete_wait
|
jpayne@68
|
1830 frame_paren1: Frame
|
jpayne@68
|
1831 paren_style_title: Label
|
jpayne@68
|
1832 (*)paren_style_type: OptionMenu - paren_style
|
jpayne@68
|
1833 frame_paren2: Frame
|
jpayne@68
|
1834 paren_time_title: Label
|
jpayne@68
|
1835 (*)paren_flash_time: Entry - flash_delay
|
jpayne@68
|
1836 (*)bell_on: Checkbutton - paren_bell
|
jpayne@68
|
1837 frame_editor: LabelFrame
|
jpayne@68
|
1838 frame_save: Frame
|
jpayne@68
|
1839 run_save_title: Label
|
jpayne@68
|
1840 (*)save_ask_on: Radiobutton - autosave
|
jpayne@68
|
1841 (*)save_auto_on: Radiobutton - autosave
|
jpayne@68
|
1842 frame_format: Frame
|
jpayne@68
|
1843 format_width_title: Label
|
jpayne@68
|
1844 (*)format_width_int: Entry - format_width
|
jpayne@68
|
1845 frame_line_numbers_default: Frame
|
jpayne@68
|
1846 line_numbers_default_title: Label
|
jpayne@68
|
1847 (*)line_numbers_default_bool: Checkbutton - line_numbers_default
|
jpayne@68
|
1848 frame_context: Frame
|
jpayne@68
|
1849 context_title: Label
|
jpayne@68
|
1850 (*)context_int: Entry - context_lines
|
jpayne@68
|
1851 frame_shell: LabelFrame
|
jpayne@68
|
1852 frame_auto_squeeze_min_lines: Frame
|
jpayne@68
|
1853 auto_squeeze_min_lines_title: Label
|
jpayne@68
|
1854 (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
|
jpayne@68
|
1855 frame_help: LabelFrame
|
jpayne@68
|
1856 frame_helplist: Frame
|
jpayne@68
|
1857 frame_helplist_buttons: Frame
|
jpayne@68
|
1858 (*)button_helplist_edit
|
jpayne@68
|
1859 (*)button_helplist_add
|
jpayne@68
|
1860 (*)button_helplist_remove
|
jpayne@68
|
1861 (*)helplist: ListBox
|
jpayne@68
|
1862 scroll_helplist: Scrollbar
|
jpayne@68
|
1863 """
|
jpayne@68
|
1864 # Integer values need StringVar because int('') raises.
|
jpayne@68
|
1865 self.startup_edit = tracers.add(
|
jpayne@68
|
1866 IntVar(self), ('main', 'General', 'editor-on-startup'))
|
jpayne@68
|
1867 self.win_width = tracers.add(
|
jpayne@68
|
1868 StringVar(self), ('main', 'EditorWindow', 'width'))
|
jpayne@68
|
1869 self.win_height = tracers.add(
|
jpayne@68
|
1870 StringVar(self), ('main', 'EditorWindow', 'height'))
|
jpayne@68
|
1871 self.cursor_blink = tracers.add(
|
jpayne@68
|
1872 BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
|
jpayne@68
|
1873 self.autocomplete_wait = tracers.add(
|
jpayne@68
|
1874 StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
|
jpayne@68
|
1875 self.paren_style = tracers.add(
|
jpayne@68
|
1876 StringVar(self), ('extensions', 'ParenMatch', 'style'))
|
jpayne@68
|
1877 self.flash_delay = tracers.add(
|
jpayne@68
|
1878 StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
|
jpayne@68
|
1879 self.paren_bell = tracers.add(
|
jpayne@68
|
1880 BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
|
jpayne@68
|
1881
|
jpayne@68
|
1882 self.auto_squeeze_min_lines = tracers.add(
|
jpayne@68
|
1883 StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
|
jpayne@68
|
1884
|
jpayne@68
|
1885 self.autosave = tracers.add(
|
jpayne@68
|
1886 IntVar(self), ('main', 'General', 'autosave'))
|
jpayne@68
|
1887 self.format_width = tracers.add(
|
jpayne@68
|
1888 StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
|
jpayne@68
|
1889 self.line_numbers_default = tracers.add(
|
jpayne@68
|
1890 BooleanVar(self),
|
jpayne@68
|
1891 ('main', 'EditorWindow', 'line-numbers-default'))
|
jpayne@68
|
1892 self.context_lines = tracers.add(
|
jpayne@68
|
1893 StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
|
jpayne@68
|
1894
|
jpayne@68
|
1895 # Create widgets:
|
jpayne@68
|
1896 # Section frames.
|
jpayne@68
|
1897 frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
1898 text=' Window Preferences')
|
jpayne@68
|
1899 frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
1900 text=' Editor Preferences')
|
jpayne@68
|
1901 frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
1902 text=' Shell Preferences')
|
jpayne@68
|
1903 frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
jpayne@68
|
1904 text=' Additional Help Sources ')
|
jpayne@68
|
1905 # Frame_window.
|
jpayne@68
|
1906 frame_run = Frame(frame_window, borderwidth=0)
|
jpayne@68
|
1907 startup_title = Label(frame_run, text='At Startup')
|
jpayne@68
|
1908 self.startup_editor_on = Radiobutton(
|
jpayne@68
|
1909 frame_run, variable=self.startup_edit, value=1,
|
jpayne@68
|
1910 text="Open Edit Window")
|
jpayne@68
|
1911 self.startup_shell_on = Radiobutton(
|
jpayne@68
|
1912 frame_run, variable=self.startup_edit, value=0,
|
jpayne@68
|
1913 text='Open Shell Window')
|
jpayne@68
|
1914
|
jpayne@68
|
1915 frame_win_size = Frame(frame_window, borderwidth=0)
|
jpayne@68
|
1916 win_size_title = Label(
|
jpayne@68
|
1917 frame_win_size, text='Initial Window Size (in characters)')
|
jpayne@68
|
1918 win_width_title = Label(frame_win_size, text='Width')
|
jpayne@68
|
1919 self.win_width_int = Entry(
|
jpayne@68
|
1920 frame_win_size, textvariable=self.win_width, width=3,
|
jpayne@68
|
1921 validatecommand=self.digits_only, validate='key',
|
jpayne@68
|
1922 )
|
jpayne@68
|
1923 win_height_title = Label(frame_win_size, text='Height')
|
jpayne@68
|
1924 self.win_height_int = Entry(
|
jpayne@68
|
1925 frame_win_size, textvariable=self.win_height, width=3,
|
jpayne@68
|
1926 validatecommand=self.digits_only, validate='key',
|
jpayne@68
|
1927 )
|
jpayne@68
|
1928
|
jpayne@68
|
1929 frame_cursor_blink = Frame(frame_window, borderwidth=0)
|
jpayne@68
|
1930 cursor_blink_title = Label(frame_cursor_blink, text='Cursor Blink')
|
jpayne@68
|
1931 self.cursor_blink_bool = Checkbutton(frame_cursor_blink,
|
jpayne@68
|
1932 variable=self.cursor_blink, width=1)
|
jpayne@68
|
1933
|
jpayne@68
|
1934 frame_autocomplete = Frame(frame_window, borderwidth=0,)
|
jpayne@68
|
1935 auto_wait_title = Label(frame_autocomplete,
|
jpayne@68
|
1936 text='Completions Popup Wait (milliseconds)')
|
jpayne@68
|
1937 self.auto_wait_int = Entry(frame_autocomplete, width=6,
|
jpayne@68
|
1938 textvariable=self.autocomplete_wait,
|
jpayne@68
|
1939 validatecommand=self.digits_only,
|
jpayne@68
|
1940 validate='key',
|
jpayne@68
|
1941 )
|
jpayne@68
|
1942
|
jpayne@68
|
1943 frame_paren1 = Frame(frame_window, borderwidth=0)
|
jpayne@68
|
1944 paren_style_title = Label(frame_paren1, text='Paren Match Style')
|
jpayne@68
|
1945 self.paren_style_type = OptionMenu(
|
jpayne@68
|
1946 frame_paren1, self.paren_style, 'expression',
|
jpayne@68
|
1947 "opener","parens","expression")
|
jpayne@68
|
1948 frame_paren2 = Frame(frame_window, borderwidth=0)
|
jpayne@68
|
1949 paren_time_title = Label(
|
jpayne@68
|
1950 frame_paren2, text='Time Match Displayed (milliseconds)\n'
|
jpayne@68
|
1951 '(0 is until next input)')
|
jpayne@68
|
1952 self.paren_flash_time = Entry(
|
jpayne@68
|
1953 frame_paren2, textvariable=self.flash_delay, width=6)
|
jpayne@68
|
1954 self.bell_on = Checkbutton(
|
jpayne@68
|
1955 frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
|
jpayne@68
|
1956
|
jpayne@68
|
1957 # Frame_editor.
|
jpayne@68
|
1958 frame_save = Frame(frame_editor, borderwidth=0)
|
jpayne@68
|
1959 run_save_title = Label(frame_save, text='At Start of Run (F5) ')
|
jpayne@68
|
1960 self.save_ask_on = Radiobutton(
|
jpayne@68
|
1961 frame_save, variable=self.autosave, value=0,
|
jpayne@68
|
1962 text="Prompt to Save")
|
jpayne@68
|
1963 self.save_auto_on = Radiobutton(
|
jpayne@68
|
1964 frame_save, variable=self.autosave, value=1,
|
jpayne@68
|
1965 text='No Prompt')
|
jpayne@68
|
1966
|
jpayne@68
|
1967 frame_format = Frame(frame_editor, borderwidth=0)
|
jpayne@68
|
1968 format_width_title = Label(frame_format,
|
jpayne@68
|
1969 text='Format Paragraph Max Width')
|
jpayne@68
|
1970 self.format_width_int = Entry(
|
jpayne@68
|
1971 frame_format, textvariable=self.format_width, width=4,
|
jpayne@68
|
1972 validatecommand=self.digits_only, validate='key',
|
jpayne@68
|
1973 )
|
jpayne@68
|
1974
|
jpayne@68
|
1975 frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
|
jpayne@68
|
1976 line_numbers_default_title = Label(
|
jpayne@68
|
1977 frame_line_numbers_default, text='Show line numbers in new windows')
|
jpayne@68
|
1978 self.line_numbers_default_bool = Checkbutton(
|
jpayne@68
|
1979 frame_line_numbers_default,
|
jpayne@68
|
1980 variable=self.line_numbers_default,
|
jpayne@68
|
1981 width=1)
|
jpayne@68
|
1982
|
jpayne@68
|
1983 frame_context = Frame(frame_editor, borderwidth=0)
|
jpayne@68
|
1984 context_title = Label(frame_context, text='Max Context Lines :')
|
jpayne@68
|
1985 self.context_int = Entry(
|
jpayne@68
|
1986 frame_context, textvariable=self.context_lines, width=3,
|
jpayne@68
|
1987 validatecommand=self.digits_only, validate='key',
|
jpayne@68
|
1988 )
|
jpayne@68
|
1989
|
jpayne@68
|
1990 # Frame_shell.
|
jpayne@68
|
1991 frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
|
jpayne@68
|
1992 auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
|
jpayne@68
|
1993 text='Auto-Squeeze Min. Lines:')
|
jpayne@68
|
1994 self.auto_squeeze_min_lines_int = Entry(
|
jpayne@68
|
1995 frame_auto_squeeze_min_lines, width=4,
|
jpayne@68
|
1996 textvariable=self.auto_squeeze_min_lines,
|
jpayne@68
|
1997 validatecommand=self.digits_only, validate='key',
|
jpayne@68
|
1998 )
|
jpayne@68
|
1999
|
jpayne@68
|
2000 # frame_help.
|
jpayne@68
|
2001 frame_helplist = Frame(frame_help)
|
jpayne@68
|
2002 frame_helplist_buttons = Frame(frame_helplist)
|
jpayne@68
|
2003 self.helplist = Listbox(
|
jpayne@68
|
2004 frame_helplist, height=5, takefocus=True,
|
jpayne@68
|
2005 exportselection=FALSE)
|
jpayne@68
|
2006 scroll_helplist = Scrollbar(frame_helplist)
|
jpayne@68
|
2007 scroll_helplist['command'] = self.helplist.yview
|
jpayne@68
|
2008 self.helplist['yscrollcommand'] = scroll_helplist.set
|
jpayne@68
|
2009 self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
|
jpayne@68
|
2010 self.button_helplist_edit = Button(
|
jpayne@68
|
2011 frame_helplist_buttons, text='Edit', state='disabled',
|
jpayne@68
|
2012 width=8, command=self.helplist_item_edit)
|
jpayne@68
|
2013 self.button_helplist_add = Button(
|
jpayne@68
|
2014 frame_helplist_buttons, text='Add',
|
jpayne@68
|
2015 width=8, command=self.helplist_item_add)
|
jpayne@68
|
2016 self.button_helplist_remove = Button(
|
jpayne@68
|
2017 frame_helplist_buttons, text='Remove', state='disabled',
|
jpayne@68
|
2018 width=8, command=self.helplist_item_remove)
|
jpayne@68
|
2019
|
jpayne@68
|
2020 # Pack widgets:
|
jpayne@68
|
2021 # Body.
|
jpayne@68
|
2022 frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
2023 frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
2024 frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
2025 frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
2026 # frame_run.
|
jpayne@68
|
2027 frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2028 startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2029 self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2030 self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2031 # frame_win_size.
|
jpayne@68
|
2032 frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2033 win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2034 self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
|
jpayne@68
|
2035 win_height_title.pack(side=RIGHT, anchor=E, pady=5)
|
jpayne@68
|
2036 self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
|
jpayne@68
|
2037 win_width_title.pack(side=RIGHT, anchor=E, pady=5)
|
jpayne@68
|
2038 # frame_cursor_blink.
|
jpayne@68
|
2039 frame_cursor_blink.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2040 cursor_blink_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2041 self.cursor_blink_bool.pack(side=LEFT, padx=5, pady=5)
|
jpayne@68
|
2042 # frame_autocomplete.
|
jpayne@68
|
2043 frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2044 auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2045 self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
|
jpayne@68
|
2046 # frame_paren.
|
jpayne@68
|
2047 frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2048 paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2049 self.paren_style_type.pack(side=TOP, padx=10, pady=5)
|
jpayne@68
|
2050 frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2051 paren_time_title.pack(side=LEFT, anchor=W, padx=5)
|
jpayne@68
|
2052 self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
|
jpayne@68
|
2053 self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
|
jpayne@68
|
2054
|
jpayne@68
|
2055 # frame_save.
|
jpayne@68
|
2056 frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2057 run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2058 self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2059 self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2060 # frame_format.
|
jpayne@68
|
2061 frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2062 format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2063 self.format_width_int.pack(side=TOP, padx=10, pady=5)
|
jpayne@68
|
2064 # frame_line_numbers_default.
|
jpayne@68
|
2065 frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2066 line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2067 self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
|
jpayne@68
|
2068 # frame_context.
|
jpayne@68
|
2069 frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2070 context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2071 self.context_int.pack(side=TOP, padx=5, pady=5)
|
jpayne@68
|
2072
|
jpayne@68
|
2073 # frame_auto_squeeze_min_lines
|
jpayne@68
|
2074 frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
|
jpayne@68
|
2075 auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
|
jpayne@68
|
2076 self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
|
jpayne@68
|
2077
|
jpayne@68
|
2078 # frame_help.
|
jpayne@68
|
2079 frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
|
jpayne@68
|
2080 frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
jpayne@68
|
2081 scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
|
jpayne@68
|
2082 self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
|
jpayne@68
|
2083 self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
|
jpayne@68
|
2084 self.button_helplist_add.pack(side=TOP, anchor=W)
|
jpayne@68
|
2085 self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
|
jpayne@68
|
2086
|
jpayne@68
|
2087 def load_general_cfg(self):
|
jpayne@68
|
2088 "Load current configuration settings for the general options."
|
jpayne@68
|
2089 # Set variables for all windows.
|
jpayne@68
|
2090 self.startup_edit.set(idleConf.GetOption(
|
jpayne@68
|
2091 'main', 'General', 'editor-on-startup', type='bool'))
|
jpayne@68
|
2092 self.win_width.set(idleConf.GetOption(
|
jpayne@68
|
2093 'main', 'EditorWindow', 'width', type='int'))
|
jpayne@68
|
2094 self.win_height.set(idleConf.GetOption(
|
jpayne@68
|
2095 'main', 'EditorWindow', 'height', type='int'))
|
jpayne@68
|
2096 self.cursor_blink.set(idleConf.GetOption(
|
jpayne@68
|
2097 'main', 'EditorWindow', 'cursor-blink', type='bool'))
|
jpayne@68
|
2098 self.autocomplete_wait.set(idleConf.GetOption(
|
jpayne@68
|
2099 'extensions', 'AutoComplete', 'popupwait', type='int'))
|
jpayne@68
|
2100 self.paren_style.set(idleConf.GetOption(
|
jpayne@68
|
2101 'extensions', 'ParenMatch', 'style'))
|
jpayne@68
|
2102 self.flash_delay.set(idleConf.GetOption(
|
jpayne@68
|
2103 'extensions', 'ParenMatch', 'flash-delay', type='int'))
|
jpayne@68
|
2104 self.paren_bell.set(idleConf.GetOption(
|
jpayne@68
|
2105 'extensions', 'ParenMatch', 'bell'))
|
jpayne@68
|
2106
|
jpayne@68
|
2107 # Set variables for editor windows.
|
jpayne@68
|
2108 self.autosave.set(idleConf.GetOption(
|
jpayne@68
|
2109 'main', 'General', 'autosave', default=0, type='bool'))
|
jpayne@68
|
2110 self.format_width.set(idleConf.GetOption(
|
jpayne@68
|
2111 'extensions', 'FormatParagraph', 'max-width', type='int'))
|
jpayne@68
|
2112 self.line_numbers_default.set(idleConf.GetOption(
|
jpayne@68
|
2113 'main', 'EditorWindow', 'line-numbers-default', type='bool'))
|
jpayne@68
|
2114 self.context_lines.set(idleConf.GetOption(
|
jpayne@68
|
2115 'extensions', 'CodeContext', 'maxlines', type='int'))
|
jpayne@68
|
2116
|
jpayne@68
|
2117 # Set variables for shell windows.
|
jpayne@68
|
2118 self.auto_squeeze_min_lines.set(idleConf.GetOption(
|
jpayne@68
|
2119 'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
|
jpayne@68
|
2120
|
jpayne@68
|
2121 # Set additional help sources.
|
jpayne@68
|
2122 self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
|
jpayne@68
|
2123 self.helplist.delete(0, 'end')
|
jpayne@68
|
2124 for help_item in self.user_helplist:
|
jpayne@68
|
2125 self.helplist.insert(END, help_item[0])
|
jpayne@68
|
2126 self.set_add_delete_state()
|
jpayne@68
|
2127
|
jpayne@68
|
2128 def help_source_selected(self, event):
|
jpayne@68
|
2129 "Handle event for selecting additional help."
|
jpayne@68
|
2130 self.set_add_delete_state()
|
jpayne@68
|
2131
|
jpayne@68
|
2132 def set_add_delete_state(self):
|
jpayne@68
|
2133 "Toggle the state for the help list buttons based on list entries."
|
jpayne@68
|
2134 if self.helplist.size() < 1: # No entries in list.
|
jpayne@68
|
2135 self.button_helplist_edit.state(('disabled',))
|
jpayne@68
|
2136 self.button_helplist_remove.state(('disabled',))
|
jpayne@68
|
2137 else: # Some entries.
|
jpayne@68
|
2138 if self.helplist.curselection(): # There currently is a selection.
|
jpayne@68
|
2139 self.button_helplist_edit.state(('!disabled',))
|
jpayne@68
|
2140 self.button_helplist_remove.state(('!disabled',))
|
jpayne@68
|
2141 else: # There currently is not a selection.
|
jpayne@68
|
2142 self.button_helplist_edit.state(('disabled',))
|
jpayne@68
|
2143 self.button_helplist_remove.state(('disabled',))
|
jpayne@68
|
2144
|
jpayne@68
|
2145 def helplist_item_add(self):
|
jpayne@68
|
2146 """Handle add button for the help list.
|
jpayne@68
|
2147
|
jpayne@68
|
2148 Query for name and location of new help sources and add
|
jpayne@68
|
2149 them to the list.
|
jpayne@68
|
2150 """
|
jpayne@68
|
2151 help_source = HelpSource(self, 'New Help Source').result
|
jpayne@68
|
2152 if help_source:
|
jpayne@68
|
2153 self.user_helplist.append(help_source)
|
jpayne@68
|
2154 self.helplist.insert(END, help_source[0])
|
jpayne@68
|
2155 self.update_help_changes()
|
jpayne@68
|
2156
|
jpayne@68
|
2157 def helplist_item_edit(self):
|
jpayne@68
|
2158 """Handle edit button for the help list.
|
jpayne@68
|
2159
|
jpayne@68
|
2160 Query with existing help source information and update
|
jpayne@68
|
2161 config if the values are changed.
|
jpayne@68
|
2162 """
|
jpayne@68
|
2163 item_index = self.helplist.index(ANCHOR)
|
jpayne@68
|
2164 help_source = self.user_helplist[item_index]
|
jpayne@68
|
2165 new_help_source = HelpSource(
|
jpayne@68
|
2166 self, 'Edit Help Source',
|
jpayne@68
|
2167 menuitem=help_source[0],
|
jpayne@68
|
2168 filepath=help_source[1],
|
jpayne@68
|
2169 ).result
|
jpayne@68
|
2170 if new_help_source and new_help_source != help_source:
|
jpayne@68
|
2171 self.user_helplist[item_index] = new_help_source
|
jpayne@68
|
2172 self.helplist.delete(item_index)
|
jpayne@68
|
2173 self.helplist.insert(item_index, new_help_source[0])
|
jpayne@68
|
2174 self.update_help_changes()
|
jpayne@68
|
2175 self.set_add_delete_state() # Selected will be un-selected
|
jpayne@68
|
2176
|
jpayne@68
|
2177 def helplist_item_remove(self):
|
jpayne@68
|
2178 """Handle remove button for the help list.
|
jpayne@68
|
2179
|
jpayne@68
|
2180 Delete the help list item from config.
|
jpayne@68
|
2181 """
|
jpayne@68
|
2182 item_index = self.helplist.index(ANCHOR)
|
jpayne@68
|
2183 del(self.user_helplist[item_index])
|
jpayne@68
|
2184 self.helplist.delete(item_index)
|
jpayne@68
|
2185 self.update_help_changes()
|
jpayne@68
|
2186 self.set_add_delete_state()
|
jpayne@68
|
2187
|
jpayne@68
|
2188 def update_help_changes(self):
|
jpayne@68
|
2189 "Clear and rebuild the HelpFiles section in changes"
|
jpayne@68
|
2190 changes['main']['HelpFiles'] = {}
|
jpayne@68
|
2191 for num in range(1, len(self.user_helplist) + 1):
|
jpayne@68
|
2192 changes.add_option(
|
jpayne@68
|
2193 'main', 'HelpFiles', str(num),
|
jpayne@68
|
2194 ';'.join(self.user_helplist[num-1][:2]))
|
jpayne@68
|
2195
|
jpayne@68
|
2196
|
jpayne@68
|
2197 class VarTrace:
|
jpayne@68
|
2198 """Maintain Tk variables trace state."""
|
jpayne@68
|
2199
|
jpayne@68
|
2200 def __init__(self):
|
jpayne@68
|
2201 """Store Tk variables and callbacks.
|
jpayne@68
|
2202
|
jpayne@68
|
2203 untraced: List of tuples (var, callback)
|
jpayne@68
|
2204 that do not have the callback attached
|
jpayne@68
|
2205 to the Tk var.
|
jpayne@68
|
2206 traced: List of tuples (var, callback) where
|
jpayne@68
|
2207 that callback has been attached to the var.
|
jpayne@68
|
2208 """
|
jpayne@68
|
2209 self.untraced = []
|
jpayne@68
|
2210 self.traced = []
|
jpayne@68
|
2211
|
jpayne@68
|
2212 def clear(self):
|
jpayne@68
|
2213 "Clear lists (for tests)."
|
jpayne@68
|
2214 # Call after all tests in a module to avoid memory leaks.
|
jpayne@68
|
2215 self.untraced.clear()
|
jpayne@68
|
2216 self.traced.clear()
|
jpayne@68
|
2217
|
jpayne@68
|
2218 def add(self, var, callback):
|
jpayne@68
|
2219 """Add (var, callback) tuple to untraced list.
|
jpayne@68
|
2220
|
jpayne@68
|
2221 Args:
|
jpayne@68
|
2222 var: Tk variable instance.
|
jpayne@68
|
2223 callback: Either function name to be used as a callback
|
jpayne@68
|
2224 or a tuple with IdleConf config-type, section, and
|
jpayne@68
|
2225 option names used in the default callback.
|
jpayne@68
|
2226
|
jpayne@68
|
2227 Return:
|
jpayne@68
|
2228 Tk variable instance.
|
jpayne@68
|
2229 """
|
jpayne@68
|
2230 if isinstance(callback, tuple):
|
jpayne@68
|
2231 callback = self.make_callback(var, callback)
|
jpayne@68
|
2232 self.untraced.append((var, callback))
|
jpayne@68
|
2233 return var
|
jpayne@68
|
2234
|
jpayne@68
|
2235 @staticmethod
|
jpayne@68
|
2236 def make_callback(var, config):
|
jpayne@68
|
2237 "Return default callback function to add values to changes instance."
|
jpayne@68
|
2238 def default_callback(*params):
|
jpayne@68
|
2239 "Add config values to changes instance."
|
jpayne@68
|
2240 changes.add_option(*config, var.get())
|
jpayne@68
|
2241 return default_callback
|
jpayne@68
|
2242
|
jpayne@68
|
2243 def attach(self):
|
jpayne@68
|
2244 "Attach callback to all vars that are not traced."
|
jpayne@68
|
2245 while self.untraced:
|
jpayne@68
|
2246 var, callback = self.untraced.pop()
|
jpayne@68
|
2247 var.trace_add('write', callback)
|
jpayne@68
|
2248 self.traced.append((var, callback))
|
jpayne@68
|
2249
|
jpayne@68
|
2250 def detach(self):
|
jpayne@68
|
2251 "Remove callback from traced vars."
|
jpayne@68
|
2252 while self.traced:
|
jpayne@68
|
2253 var, callback = self.traced.pop()
|
jpayne@68
|
2254 var.trace_remove('write', var.trace_info()[0][1])
|
jpayne@68
|
2255 self.untraced.append((var, callback))
|
jpayne@68
|
2256
|
jpayne@68
|
2257
|
jpayne@68
|
2258 tracers = VarTrace()
|
jpayne@68
|
2259
|
jpayne@68
|
2260 help_common = '''\
|
jpayne@68
|
2261 When you click either the Apply or Ok buttons, settings in this
|
jpayne@68
|
2262 dialog that are different from IDLE's default are saved in
|
jpayne@68
|
2263 a .idlerc directory in your home directory. Except as noted,
|
jpayne@68
|
2264 these changes apply to all versions of IDLE installed on this
|
jpayne@68
|
2265 machine. [Cancel] only cancels changes made since the last save.
|
jpayne@68
|
2266 '''
|
jpayne@68
|
2267 help_pages = {
|
jpayne@68
|
2268 'Fonts/Tabs':'''
|
jpayne@68
|
2269 Font sample: This shows what a selection of Basic Multilingual Plane
|
jpayne@68
|
2270 unicode characters look like for the current font selection. If the
|
jpayne@68
|
2271 selected font does not define a character, Tk attempts to find another
|
jpayne@68
|
2272 font that does. Substitute glyphs depend on what is available on a
|
jpayne@68
|
2273 particular system and will not necessarily have the same size as the
|
jpayne@68
|
2274 font selected. Line contains 20 characters up to Devanagari, 14 for
|
jpayne@68
|
2275 Tamil, and 10 for East Asia.
|
jpayne@68
|
2276
|
jpayne@68
|
2277 Hebrew and Arabic letters should display right to left, starting with
|
jpayne@68
|
2278 alef, \u05d0 and \u0627. Arabic digits display left to right. The
|
jpayne@68
|
2279 Devanagari and Tamil lines start with digits. The East Asian lines
|
jpayne@68
|
2280 are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
|
jpayne@68
|
2281 Hiragana and Katakana.
|
jpayne@68
|
2282
|
jpayne@68
|
2283 You can edit the font sample. Changes remain until IDLE is closed.
|
jpayne@68
|
2284 ''',
|
jpayne@68
|
2285 'Highlights': '''
|
jpayne@68
|
2286 Highlighting:
|
jpayne@68
|
2287 The IDLE Dark color theme is new in October 2015. It can only
|
jpayne@68
|
2288 be used with older IDLE releases if it is saved as a custom
|
jpayne@68
|
2289 theme, with a different name.
|
jpayne@68
|
2290 ''',
|
jpayne@68
|
2291 'Keys': '''
|
jpayne@68
|
2292 Keys:
|
jpayne@68
|
2293 The IDLE Modern Unix key set is new in June 2016. It can only
|
jpayne@68
|
2294 be used with older IDLE releases if it is saved as a custom
|
jpayne@68
|
2295 key set, with a different name.
|
jpayne@68
|
2296 ''',
|
jpayne@68
|
2297 'General': '''
|
jpayne@68
|
2298 General:
|
jpayne@68
|
2299
|
jpayne@68
|
2300 AutoComplete: Popupwait is milliseconds to wait after key char, without
|
jpayne@68
|
2301 cursor movement, before popping up completion box. Key char is '.' after
|
jpayne@68
|
2302 identifier or a '/' (or '\\' on Windows) within a string.
|
jpayne@68
|
2303
|
jpayne@68
|
2304 FormatParagraph: Max-width is max chars in lines after re-formatting.
|
jpayne@68
|
2305 Use with paragraphs in both strings and comment blocks.
|
jpayne@68
|
2306
|
jpayne@68
|
2307 ParenMatch: Style indicates what is highlighted when closer is entered:
|
jpayne@68
|
2308 'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
|
jpayne@68
|
2309 'expression' (default) - also everything in between. Flash-delay is how
|
jpayne@68
|
2310 long to highlight if cursor is not moved (0 means forever).
|
jpayne@68
|
2311
|
jpayne@68
|
2312 CodeContext: Maxlines is the maximum number of code context lines to
|
jpayne@68
|
2313 display when Code Context is turned on for an editor window.
|
jpayne@68
|
2314
|
jpayne@68
|
2315 Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
|
jpayne@68
|
2316 of output to automatically "squeeze".
|
jpayne@68
|
2317 '''
|
jpayne@68
|
2318 }
|
jpayne@68
|
2319
|
jpayne@68
|
2320
|
jpayne@68
|
2321 def is_int(s):
|
jpayne@68
|
2322 "Return 's is blank or represents an int'"
|
jpayne@68
|
2323 if not s:
|
jpayne@68
|
2324 return True
|
jpayne@68
|
2325 try:
|
jpayne@68
|
2326 int(s)
|
jpayne@68
|
2327 return True
|
jpayne@68
|
2328 except ValueError:
|
jpayne@68
|
2329 return False
|
jpayne@68
|
2330
|
jpayne@68
|
2331
|
jpayne@68
|
2332 class VerticalScrolledFrame(Frame):
|
jpayne@68
|
2333 """A pure Tkinter vertically scrollable frame.
|
jpayne@68
|
2334
|
jpayne@68
|
2335 * Use the 'interior' attribute to place widgets inside the scrollable frame
|
jpayne@68
|
2336 * Construct and pack/place/grid normally
|
jpayne@68
|
2337 * This frame only allows vertical scrolling
|
jpayne@68
|
2338 """
|
jpayne@68
|
2339 def __init__(self, parent, *args, **kw):
|
jpayne@68
|
2340 Frame.__init__(self, parent, *args, **kw)
|
jpayne@68
|
2341
|
jpayne@68
|
2342 # Create a canvas object and a vertical scrollbar for scrolling it.
|
jpayne@68
|
2343 vscrollbar = Scrollbar(self, orient=VERTICAL)
|
jpayne@68
|
2344 vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
|
jpayne@68
|
2345 canvas = Canvas(self, borderwidth=0, highlightthickness=0,
|
jpayne@68
|
2346 yscrollcommand=vscrollbar.set, width=240)
|
jpayne@68
|
2347 canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
|
jpayne@68
|
2348 vscrollbar.config(command=canvas.yview)
|
jpayne@68
|
2349
|
jpayne@68
|
2350 # Reset the view.
|
jpayne@68
|
2351 canvas.xview_moveto(0)
|
jpayne@68
|
2352 canvas.yview_moveto(0)
|
jpayne@68
|
2353
|
jpayne@68
|
2354 # Create a frame inside the canvas which will be scrolled with it.
|
jpayne@68
|
2355 self.interior = interior = Frame(canvas)
|
jpayne@68
|
2356 interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
|
jpayne@68
|
2357
|
jpayne@68
|
2358 # Track changes to the canvas and frame width and sync them,
|
jpayne@68
|
2359 # also updating the scrollbar.
|
jpayne@68
|
2360 def _configure_interior(event):
|
jpayne@68
|
2361 # Update the scrollbars to match the size of the inner frame.
|
jpayne@68
|
2362 size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
|
jpayne@68
|
2363 canvas.config(scrollregion="0 0 %s %s" % size)
|
jpayne@68
|
2364 interior.bind('<Configure>', _configure_interior)
|
jpayne@68
|
2365
|
jpayne@68
|
2366 def _configure_canvas(event):
|
jpayne@68
|
2367 if interior.winfo_reqwidth() != canvas.winfo_width():
|
jpayne@68
|
2368 # Update the inner frame's width to fill the canvas.
|
jpayne@68
|
2369 canvas.itemconfigure(interior_id, width=canvas.winfo_width())
|
jpayne@68
|
2370 canvas.bind('<Configure>', _configure_canvas)
|
jpayne@68
|
2371
|
jpayne@68
|
2372 return
|
jpayne@68
|
2373
|
jpayne@68
|
2374
|
jpayne@68
|
2375 if __name__ == '__main__':
|
jpayne@68
|
2376 from unittest import main
|
jpayne@68
|
2377 main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
|
jpayne@68
|
2378
|
jpayne@68
|
2379 from idlelib.idle_test.htest import run
|
jpayne@68
|
2380 run(ConfigDialog)
|