annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/configdialog.py @ 69:33d812a61356

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