annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/configdialog.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
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)