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