12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414 |
- """IDLE Configuration Dialog: support user customization of IDLE by GUI
- Customize font faces, sizes, and colorization attributes. Set indentation
- defaults. Customize keybindings. Colorization and keybindings can be
- saved as user defined sets. Select startup options including shell/editor
- and default window size. Define additional help sources.
- Note that tab width in IDLE is currently fixed at eight due to Tk issues.
- Refer to comments in EditorWindow autoindent code for details.
- """
- import re
- from tkinter import (Toplevel, Listbox, Canvas,
- StringVar, BooleanVar, IntVar, TRUE, FALSE,
- TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE,
- NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
- HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END, TclError)
- from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label,
- OptionMenu, Notebook, Radiobutton, Scrollbar, Style,
- Spinbox, Combobox)
- from tkinter import colorchooser
- import tkinter.font as tkfont
- from tkinter import messagebox
- from idlelib.config import idleConf, ConfigChanges
- from idlelib.config_key import GetKeysWindow
- from idlelib.dynoption import DynOptionMenu
- from idlelib import macosx
- from idlelib.query import SectionName, HelpSource
- from idlelib.textview import view_text
- from idlelib.autocomplete import AutoComplete
- from idlelib.codecontext import CodeContext
- from idlelib.parenmatch import ParenMatch
- from idlelib.format import FormatParagraph
- from idlelib.squeezer import Squeezer
- from idlelib.textview import ScrollableTextFrame
- changes = ConfigChanges()
- # Reload changed options in the following classes.
- reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
- Squeezer)
- class ConfigDialog(Toplevel):
- """Config dialog for IDLE.
- """
- def __init__(self, parent, title='', *, _htest=False, _utest=False):
- """Show the tabbed dialog for user configuration.
- Args:
- parent - parent of this dialog
- title - string which is the title of this popup dialog
- _htest - bool, change box location when running htest
- _utest - bool, don't wait_window when running unittest
- Note: Focus set on font page fontlist.
- Methods:
- create_widgets
- cancel: Bound to DELETE_WINDOW protocol.
- """
- Toplevel.__init__(self, parent)
- self.parent = parent
- if _htest:
- parent.instance_dict = {}
- if not _utest:
- self.withdraw()
- self.title(title or 'IDLE Preferences')
- x = parent.winfo_rootx() + 20
- y = parent.winfo_rooty() + (30 if not _htest else 150)
- self.geometry(f'+{x}+{y}')
- # Each theme element key is its display name.
- # The first value of the tuple is the sample area tag name.
- # The second value is the display name list sort index.
- self.create_widgets()
- self.resizable(height=FALSE, width=FALSE)
- self.transient(parent)
- self.protocol("WM_DELETE_WINDOW", self.cancel)
- self.fontpage.fontlist.focus_set()
- # XXX Decide whether to keep or delete these key bindings.
- # Key bindings for this dialog.
- # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
- # self.bind('<Alt-a>', self.Apply) #apply changes, save
- # self.bind('<F1>', self.Help) #context help
- # Attach callbacks after loading config to avoid calling them.
- tracers.attach()
- if not _utest:
- self.grab_set()
- self.wm_deiconify()
- self.wait_window()
- def create_widgets(self):
- """Create and place widgets for tabbed dialog.
- Widgets Bound to self:
- frame: encloses all other widgets
- note: Notebook
- highpage: HighPage
- fontpage: FontPage
- keyspage: KeysPage
- winpage: WinPage
- shedpage: ShedPage
- extpage: ExtPage
- Methods:
- create_action_buttons
- load_configs: Load pages except for extensions.
- activate_config_changes: Tell editors to reload.
- """
- self.frame = frame = Frame(self, padding="5px")
- self.frame.grid(sticky="nwes")
- self.note = note = Notebook(frame)
- self.extpage = ExtPage(note)
- self.highpage = HighPage(note, self.extpage)
- self.fontpage = FontPage(note, self.highpage)
- self.keyspage = KeysPage(note, self.extpage)
- self.winpage = WinPage(note)
- self.shedpage = ShedPage(note)
- note.add(self.fontpage, text=' Fonts ')
- note.add(self.highpage, text='Highlights')
- note.add(self.keyspage, text=' Keys ')
- note.add(self.winpage, text=' Windows ')
- note.add(self.shedpage, text=' Shell/Ed ')
- note.add(self.extpage, text='Extensions')
- note.enable_traversal()
- note.pack(side=TOP, expand=TRUE, fill=BOTH)
- self.create_action_buttons().pack(side=BOTTOM)
- def create_action_buttons(self):
- """Return frame of action buttons for dialog.
- Methods:
- ok
- apply
- cancel
- help
- Widget Structure:
- outer: Frame
- buttons: Frame
- (no assignment): Button (ok)
- (no assignment): Button (apply)
- (no assignment): Button (cancel)
- (no assignment): Button (help)
- (no assignment): Frame
- """
- if macosx.isAquaTk():
- # Changing the default padding on OSX results in unreadable
- # text in the buttons.
- padding_args = {}
- else:
- padding_args = {'padding': (6, 3)}
- outer = Frame(self.frame, padding=2)
- buttons_frame = Frame(outer, padding=2)
- self.buttons = {}
- for txt, cmd in (
- ('Ok', self.ok),
- ('Apply', self.apply),
- ('Cancel', self.cancel),
- ('Help', self.help)):
- self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
- takefocus=FALSE, **padding_args)
- self.buttons[txt].pack(side=LEFT, padx=5)
- # Add space above buttons.
- Frame(outer, height=2, borderwidth=0).pack(side=TOP)
- buttons_frame.pack(side=BOTTOM)
- return outer
- def ok(self):
- """Apply config changes, then dismiss dialog."""
- self.apply()
- self.destroy()
- def apply(self):
- """Apply config changes and leave dialog open."""
- self.deactivate_current_config()
- changes.save_all()
- self.extpage.save_all_changed_extensions()
- self.activate_config_changes()
- def cancel(self):
- """Dismiss config dialog.
- Methods:
- destroy: inherited
- """
- changes.clear()
- self.destroy()
- def destroy(self):
- global font_sample_text
- font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
- self.grab_release()
- super().destroy()
- def help(self):
- """Create textview for config dialog help.
- Attributes accessed:
- note
- Methods:
- view_text: Method from textview module.
- """
- page = self.note.tab(self.note.select(), option='text').strip()
- view_text(self, title='Help for IDLE preferences',
- contents=help_common+help_pages.get(page, ''))
- def deactivate_current_config(self):
- """Remove current key bindings.
- Iterate over window instances defined in parent and remove
- the keybindings.
- """
- # Before a config is saved, some cleanup of current
- # config must be done - remove the previous keybindings.
- win_instances = self.parent.instance_dict.keys()
- for instance in win_instances:
- instance.RemoveKeybindings()
- def activate_config_changes(self):
- """Apply configuration changes to current windows.
- Dynamically update the current parent window instances
- with some of the configuration changes.
- """
- win_instances = self.parent.instance_dict.keys()
- for instance in win_instances:
- instance.ResetColorizer()
- instance.ResetFont()
- instance.set_notabs_indentwidth()
- instance.ApplyKeybindings()
- instance.reset_help_menu_entries()
- instance.update_cursor_blink()
- for klass in reloadables:
- klass.reload()
- # class TabPage(Frame): # A template for Page classes.
- # def __init__(self, master):
- # super().__init__(master)
- # self.create_page_tab()
- # self.load_tab_cfg()
- # def create_page_tab(self):
- # # Define tk vars and register var and callback with tracers.
- # # Create subframes and widgets.
- # # Pack widgets.
- # def load_tab_cfg(self):
- # # Initialize widgets with data from idleConf.
- # def var_changed_var_name():
- # # For each tk var that needs other than default callback.
- # def other_methods():
- # # Define tab-specific behavior.
- font_sample_text = (
- '<ASCII/Latin1>\n'
- 'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
- '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
- '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
- '\n<IPA,Greek,Cyrillic>\n'
- '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
- '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
- '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
- '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
- '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
- '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
- '\n<Hebrew, Arabic>\n'
- '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
- '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
- '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
- '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
- '\n<Devanagari, Tamil>\n'
- '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
- '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
- '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
- '\u0b85\u0b87\u0b89\u0b8e\n'
- '\n<East Asian>\n'
- '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
- '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
- '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
- '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
- )
- class FontPage(Frame):
- def __init__(self, master, highpage):
- super().__init__(master)
- self.highlight_sample = highpage.highlight_sample
- self.create_page_font()
- self.load_font_cfg()
- def create_page_font(self):
- """Return frame of widgets for Font tab.
- Fonts: Enable users to provisionally change font face, size, or
- boldness and to see the consequence of proposed choices. Each
- action set 3 options in changes structuree and changes the
- corresponding aspect of the font sample on this page and
- highlight sample on highlight page.
- Function load_font_cfg initializes font vars and widgets from
- idleConf entries and tk.
- Fontlist: mouse button 1 click or up or down key invoke
- on_fontlist_select(), which sets var font_name.
- Sizelist: clicking the menubutton opens the dropdown menu. A
- mouse button 1 click or return key sets var font_size.
- Bold_toggle: clicking the box toggles var font_bold.
- Changing any of the font vars invokes var_changed_font, which
- adds all 3 font options to changes and calls set_samples.
- Set_samples applies a new font constructed from the font vars to
- font_sample and to highlight_sample on the highlight page.
- Widgets for FontPage(Frame): (*) widgets bound to self
- frame_font: LabelFrame
- frame_font_name: Frame
- font_name_title: Label
- (*)fontlist: ListBox - font_name
- scroll_font: Scrollbar
- frame_font_param: Frame
- font_size_title: Label
- (*)sizelist: DynOptionMenu - font_size
- (*)bold_toggle: Checkbutton - font_bold
- frame_sample: LabelFrame
- (*)font_sample: Label
- """
- self.font_name = tracers.add(StringVar(self), self.var_changed_font)
- self.font_size = tracers.add(StringVar(self), self.var_changed_font)
- self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
- # Define frames and widgets.
- frame_font = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Shell/Editor Font ')
- frame_sample = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Font Sample (Editable) ')
- # frame_font.
- frame_font_name = Frame(frame_font)
- frame_font_param = Frame(frame_font)
- font_name_title = Label(
- frame_font_name, justify=LEFT, text='Font Face :')
- self.fontlist = Listbox(frame_font_name, height=15,
- takefocus=True, exportselection=FALSE)
- self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
- self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
- self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
- scroll_font = Scrollbar(frame_font_name)
- scroll_font.config(command=self.fontlist.yview)
- self.fontlist.config(yscrollcommand=scroll_font.set)
- font_size_title = Label(frame_font_param, text='Size :')
- self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
- self.bold_toggle = Checkbutton(
- frame_font_param, variable=self.font_bold,
- onvalue=1, offvalue=0, text='Bold')
- # frame_sample.
- font_sample_frame = ScrollableTextFrame(frame_sample)
- self.font_sample = font_sample_frame.text
- self.font_sample.config(wrap=NONE, width=1, height=1)
- self.font_sample.insert(END, font_sample_text)
- # Grid and pack widgets:
- self.columnconfigure(1, weight=1)
- self.rowconfigure(2, weight=1)
- frame_font.grid(row=0, column=0, padx=5, pady=5)
- frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
- sticky='nsew')
- # frame_font.
- frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
- font_name_title.pack(side=TOP, anchor=W)
- self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
- scroll_font.pack(side=LEFT, fill=Y)
- font_size_title.pack(side=LEFT, anchor=W)
- self.sizelist.pack(side=LEFT, anchor=W)
- self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
- # frame_sample.
- font_sample_frame.pack(expand=TRUE, fill=BOTH)
- def load_font_cfg(self):
- """Load current configuration settings for the font options.
- Retrieve current font with idleConf.GetFont and font families
- from tk. Setup fontlist and set font_name. Setup sizelist,
- which sets font_size. Set font_bold. Call set_samples.
- """
- configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
- font_name = configured_font[0].lower()
- font_size = configured_font[1]
- font_bold = configured_font[2]=='bold'
- # Set sorted no-duplicate editor font selection list and font_name.
- fonts = sorted(set(tkfont.families(self)))
- for font in fonts:
- self.fontlist.insert(END, font)
- self.font_name.set(font_name)
- lc_fonts = [s.lower() for s in fonts]
- try:
- current_font_index = lc_fonts.index(font_name)
- self.fontlist.see(current_font_index)
- self.fontlist.select_set(current_font_index)
- self.fontlist.select_anchor(current_font_index)
- self.fontlist.activate(current_font_index)
- except ValueError:
- pass
- # Set font size dropdown.
- self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
- '16', '18', '20', '22', '25', '29', '34', '40'),
- font_size)
- # Set font weight.
- self.font_bold.set(font_bold)
- self.set_samples()
- def var_changed_font(self, *params):
- """Store changes to font attributes.
- When one font attribute changes, save them all, as they are
- not independent from each other. In particular, when we are
- overriding the default font, we need to write out everything.
- """
- value = self.font_name.get()
- changes.add_option('main', 'EditorWindow', 'font', value)
- value = self.font_size.get()
- changes.add_option('main', 'EditorWindow', 'font-size', value)
- value = self.font_bold.get()
- changes.add_option('main', 'EditorWindow', 'font-bold', value)
- self.set_samples()
- def on_fontlist_select(self, event):
- """Handle selecting a font from the list.
- Event can result from either mouse click or Up or Down key.
- Set font_name and example displays to selection.
- """
- font = self.fontlist.get(
- ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
- self.font_name.set(font.lower())
- def set_samples(self, event=None):
- """Update update both screen samples with the font settings.
- Called on font initialization and change events.
- Accesses font_name, font_size, and font_bold Variables.
- Updates font_sample and highlight page highlight_sample.
- """
- font_name = self.font_name.get()
- font_weight = tkfont.BOLD if self.font_bold.get() else tkfont.NORMAL
- new_font = (font_name, self.font_size.get(), font_weight)
- self.font_sample['font'] = new_font
- self.highlight_sample['font'] = new_font
- class HighPage(Frame):
- def __init__(self, master, extpage):
- super().__init__(master)
- self.extpage = extpage
- self.cd = master.winfo_toplevel()
- self.style = Style(master)
- self.create_page_highlight()
- self.load_theme_cfg()
- def create_page_highlight(self):
- """Return frame of widgets for Highlights tab.
- Enable users to provisionally change foreground and background
- colors applied to textual tags. Color mappings are stored in
- complete listings called themes. Built-in themes in
- idlelib/config-highlight.def are fixed as far as the dialog is
- concerned. Any theme can be used as the base for a new custom
- theme, stored in .idlerc/config-highlight.cfg.
- Function load_theme_cfg() initializes tk variables and theme
- lists and calls paint_theme_sample() and set_highlight_target()
- for the current theme. Radiobuttons builtin_theme_on and
- custom_theme_on toggle var theme_source, which controls if the
- current set of colors are from a builtin or custom theme.
- DynOptionMenus builtinlist and customlist contain lists of the
- builtin and custom themes, respectively, and the current item
- from each list is stored in vars builtin_name and custom_name.
- Function paint_theme_sample() applies the colors from the theme
- to the tags in text widget highlight_sample and then invokes
- set_color_sample(). Function set_highlight_target() sets the state
- of the radiobuttons fg_on and bg_on based on the tag and it also
- invokes set_color_sample().
- Function set_color_sample() sets the background color for the frame
- holding the color selector. This provides a larger visual of the
- color for the current tag and plane (foreground/background).
- Note: set_color_sample() is called from many places and is often
- called more than once when a change is made. It is invoked when
- foreground or background is selected (radiobuttons), from
- paint_theme_sample() (theme is changed or load_cfg is called), and
- from set_highlight_target() (target tag is changed or load_cfg called).
- Button delete_custom invokes delete_custom() to delete
- a custom theme from idleConf.userCfg['highlight'] and changes.
- Button save_custom invokes save_as_new_theme() which calls
- get_new_theme_name() and create_new() to save a custom theme
- and its colors to idleConf.userCfg['highlight'].
- Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
- if the current selected color for a tag is for the foreground or
- background.
- DynOptionMenu targetlist contains a readable description of the
- tags applied to Python source within IDLE. Selecting one of the
- tags from this list populates highlight_target, which has a callback
- function set_highlight_target().
- Text widget highlight_sample displays a block of text (which is
- mock Python code) in which is embedded the defined tags and reflects
- the color attributes of the current theme and changes for those tags.
- Mouse button 1 allows for selection of a tag and updates
- highlight_target with that tag value.
- Note: The font in highlight_sample is set through the config in
- the fonts tab.
- In other words, a tag can be selected either from targetlist or
- by clicking on the sample text within highlight_sample. The
- plane (foreground/background) is selected via the radiobutton.
- Together, these two (tag and plane) control what color is
- shown in set_color_sample() for the current theme. Button set_color
- invokes get_color() which displays a ColorChooser to change the
- color for the selected tag/plane. If a new color is picked,
- it will be saved to changes and the highlight_sample and
- frame background will be updated.
- Tk Variables:
- color: Color of selected target.
- builtin_name: Menu variable for built-in theme.
- custom_name: Menu variable for custom theme.
- fg_bg_toggle: Toggle for foreground/background color.
- Note: this has no callback.
- theme_source: Selector for built-in or custom theme.
- highlight_target: Menu variable for the highlight tag target.
- Instance Data Attributes:
- theme_elements: Dictionary of tags for text highlighting.
- The key is the display name and the value is a tuple of
- (tag name, display sort order).
- Methods [attachment]:
- load_theme_cfg: Load current highlight colors.
- get_color: Invoke colorchooser [button_set_color].
- set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
- set_highlight_target: set fg_bg_toggle, set_color_sample().
- set_color_sample: Set frame background to target.
- on_new_color_set: Set new color and add option.
- paint_theme_sample: Recolor sample.
- get_new_theme_name: Get from popup.
- create_new: Combine theme with changes and save.
- save_as_new_theme: Save [button_save_custom].
- set_theme_type: Command for [theme_source].
- delete_custom: Activate default [button_delete_custom].
- save_new: Save to userCfg['theme'] (is function).
- Widgets of highlights page frame: (*) widgets bound to self
- frame_custom: LabelFrame
- (*)highlight_sample: Text
- (*)frame_color_set: Frame
- (*)button_set_color: Button
- (*)targetlist: DynOptionMenu - highlight_target
- frame_fg_bg_toggle: Frame
- (*)fg_on: Radiobutton - fg_bg_toggle
- (*)bg_on: Radiobutton - fg_bg_toggle
- (*)button_save_custom: Button
- frame_theme: LabelFrame
- theme_type_title: Label
- (*)builtin_theme_on: Radiobutton - theme_source
- (*)custom_theme_on: Radiobutton - theme_source
- (*)builtinlist: DynOptionMenu - builtin_name
- (*)customlist: DynOptionMenu - custom_name
- (*)button_delete_custom: Button
- (*)theme_message: Label
- """
- self.theme_elements = {
- 'Normal Code or Text': ('normal', '00'),
- 'Code Context': ('context', '01'),
- 'Python Keywords': ('keyword', '02'),
- 'Python Definitions': ('definition', '03'),
- 'Python Builtins': ('builtin', '04'),
- 'Python Comments': ('comment', '05'),
- 'Python Strings': ('string', '06'),
- 'Selected Text': ('hilite', '07'),
- 'Found Text': ('hit', '08'),
- 'Cursor': ('cursor', '09'),
- 'Editor Breakpoint': ('break', '10'),
- 'Shell Prompt': ('console', '11'),
- 'Error Text': ('error', '12'),
- 'Shell User Output': ('stdout', '13'),
- 'Shell User Exception': ('stderr', '14'),
- 'Line Number': ('linenumber', '16'),
- }
- self.builtin_name = tracers.add(
- StringVar(self), self.var_changed_builtin_name)
- self.custom_name = tracers.add(
- StringVar(self), self.var_changed_custom_name)
- self.fg_bg_toggle = BooleanVar(self)
- self.color = tracers.add(
- StringVar(self), self.var_changed_color)
- self.theme_source = tracers.add(
- BooleanVar(self), self.var_changed_theme_source)
- self.highlight_target = tracers.add(
- StringVar(self), self.var_changed_highlight_target)
- # Create widgets:
- # body frame and section frames.
- frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Custom Highlighting ')
- frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Highlighting Theme ')
- # frame_custom.
- sample_frame = ScrollableTextFrame(
- frame_custom, relief=SOLID, borderwidth=1)
- text = self.highlight_sample = sample_frame.text
- text.configure(
- font=('courier', 12, ''), cursor='hand2', width=1, height=1,
- takefocus=FALSE, highlightthickness=0, wrap=NONE)
- # Prevent perhaps invisible selection of word or slice.
- text.bind('<Double-Button-1>', lambda e: 'break')
- text.bind('<B1-Motion>', lambda e: 'break')
- string_tags=(
- ('# Click selects item.', 'comment'), ('\n', 'normal'),
- ('code context section', 'context'), ('\n', 'normal'),
- ('| cursor', 'cursor'), ('\n', 'normal'),
- ('def', 'keyword'), (' ', 'normal'),
- ('func', 'definition'), ('(param):\n ', 'normal'),
- ('"Return None."', 'string'), ('\n var0 = ', 'normal'),
- ("'string'", 'string'), ('\n var1 = ', 'normal'),
- ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
- ("'found'", 'hit'), ('\n var3 = ', 'normal'),
- ('list', 'builtin'), ('(', 'normal'),
- ('None', 'keyword'), (')\n', 'normal'),
- (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
- ('>>>', 'console'), (' 3.14**2\n', 'normal'),
- ('9.8596', 'stdout'), ('\n', 'normal'),
- ('>>>', 'console'), (' pri ', 'normal'),
- ('n', 'error'), ('t(\n', 'normal'),
- ('SyntaxError', 'stderr'), ('\n', 'normal'))
- for string, tag in string_tags:
- text.insert(END, string, tag)
- n_lines = len(text.get('1.0', END).splitlines())
- for lineno in range(1, n_lines):
- text.insert(f'{lineno}.0',
- f'{lineno:{len(str(n_lines))}d} ',
- 'linenumber')
- for element in self.theme_elements:
- def tem(event, elem=element):
- # event.widget.winfo_top_level().highlight_target.set(elem)
- self.highlight_target.set(elem)
- text.tag_bind(
- self.theme_elements[element][0], '<ButtonPress-1>', tem)
- text['state'] = 'disabled'
- self.style.configure('frame_color_set.TFrame', borderwidth=1,
- relief='solid')
- self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
- frame_fg_bg_toggle = Frame(frame_custom)
- self.button_set_color = Button(
- self.frame_color_set, text='Choose Color for :',
- command=self.get_color)
- self.targetlist = DynOptionMenu(
- self.frame_color_set, self.highlight_target, None,
- highlightthickness=0) #, command=self.set_highlight_targetBinding
- self.fg_on = Radiobutton(
- frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
- text='Foreground', command=self.set_color_sample_binding)
- self.bg_on = Radiobutton(
- frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
- text='Background', command=self.set_color_sample_binding)
- self.fg_bg_toggle.set(1)
- self.button_save_custom = Button(
- frame_custom, text='Save as New Custom Theme',
- command=self.save_as_new_theme)
- # frame_theme.
- theme_type_title = Label(frame_theme, text='Select : ')
- self.builtin_theme_on = Radiobutton(
- frame_theme, variable=self.theme_source, value=1,
- command=self.set_theme_type, text='a Built-in Theme')
- self.custom_theme_on = Radiobutton(
- frame_theme, variable=self.theme_source, value=0,
- command=self.set_theme_type, text='a Custom Theme')
- self.builtinlist = DynOptionMenu(
- frame_theme, self.builtin_name, None, command=None)
- self.customlist = DynOptionMenu(
- frame_theme, self.custom_name, None, command=None)
- self.button_delete_custom = Button(
- frame_theme, text='Delete Custom Theme',
- command=self.delete_custom)
- self.theme_message = Label(frame_theme, borderwidth=2)
- # Pack widgets:
- # body.
- frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
- frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
- # frame_custom.
- self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
- sample_frame.pack(
- side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
- self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
- self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
- self.fg_on.pack(side=LEFT, anchor=E)
- self.bg_on.pack(side=RIGHT, anchor=W)
- self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
- # frame_theme.
- theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
- self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
- self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
- self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
- self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
- self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
- self.theme_message.pack(side=TOP, fill=X, pady=5)
- def load_theme_cfg(self):
- """Load current configuration settings for the theme options.
- Based on the theme_source toggle, the theme is set as
- either builtin or custom and the initial widget values
- reflect the current settings from idleConf.
- Attributes updated:
- theme_source: Set from idleConf.
- builtinlist: List of default themes from idleConf.
- customlist: List of custom themes from idleConf.
- custom_theme_on: Disabled if there are no custom themes.
- custom_theme: Message with additional information.
- targetlist: Create menu from self.theme_elements.
- Methods:
- set_theme_type
- paint_theme_sample
- set_highlight_target
- """
- # Set current theme type radiobutton.
- self.theme_source.set(idleConf.GetOption(
- 'main', 'Theme', 'default', type='bool', default=1))
- # Set current theme.
- current_option = idleConf.CurrentTheme()
- # Load available theme option menus.
- if self.theme_source.get(): # Default theme selected.
- item_list = idleConf.GetSectionList('default', 'highlight')
- item_list.sort()
- self.builtinlist.SetMenu(item_list, current_option)
- item_list = idleConf.GetSectionList('user', 'highlight')
- item_list.sort()
- if not item_list:
- self.custom_theme_on.state(('disabled',))
- self.custom_name.set('- no custom themes -')
- else:
- self.customlist.SetMenu(item_list, item_list[0])
- else: # User theme selected.
- item_list = idleConf.GetSectionList('user', 'highlight')
- item_list.sort()
- self.customlist.SetMenu(item_list, current_option)
- item_list = idleConf.GetSectionList('default', 'highlight')
- item_list.sort()
- self.builtinlist.SetMenu(item_list, item_list[0])
- self.set_theme_type()
- # Load theme element option menu.
- theme_names = list(self.theme_elements.keys())
- theme_names.sort(key=lambda x: self.theme_elements[x][1])
- self.targetlist.SetMenu(theme_names, theme_names[0])
- self.paint_theme_sample()
- self.set_highlight_target()
- def var_changed_builtin_name(self, *params):
- """Process new builtin theme selection.
- Add the changed theme's name to the changed_items and recreate
- the sample with the values from the selected theme.
- """
- old_themes = ('IDLE Classic', 'IDLE New')
- value = self.builtin_name.get()
- if value not in old_themes:
- if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
- changes.add_option('main', 'Theme', 'name', old_themes[0])
- changes.add_option('main', 'Theme', 'name2', value)
- self.theme_message['text'] = 'New theme, see Help'
- else:
- changes.add_option('main', 'Theme', 'name', value)
- changes.add_option('main', 'Theme', 'name2', '')
- self.theme_message['text'] = ''
- self.paint_theme_sample()
- def var_changed_custom_name(self, *params):
- """Process new custom theme selection.
- If a new custom theme is selected, add the name to the
- changed_items and apply the theme to the sample.
- """
- value = self.custom_name.get()
- if value != '- no custom themes -':
- changes.add_option('main', 'Theme', 'name', value)
- self.paint_theme_sample()
- def var_changed_theme_source(self, *params):
- """Process toggle between builtin and custom theme.
- Update the default toggle value and apply the newly
- selected theme type.
- """
- value = self.theme_source.get()
- changes.add_option('main', 'Theme', 'default', value)
- if value:
- self.var_changed_builtin_name()
- else:
- self.var_changed_custom_name()
- def var_changed_color(self, *params):
- "Process change to color choice."
- self.on_new_color_set()
- def var_changed_highlight_target(self, *params):
- "Process selection of new target tag for highlighting."
- self.set_highlight_target()
- def set_theme_type(self):
- """Set available screen options based on builtin or custom theme.
- Attributes accessed:
- theme_source
- Attributes updated:
- builtinlist
- customlist
- button_delete_custom
- custom_theme_on
- Called from:
- handler for builtin_theme_on and custom_theme_on
- delete_custom
- create_new
- load_theme_cfg
- """
- if self.theme_source.get():
- self.builtinlist['state'] = 'normal'
- self.customlist['state'] = 'disabled'
- self.button_delete_custom.state(('disabled',))
- else:
- self.builtinlist['state'] = 'disabled'
- self.custom_theme_on.state(('!disabled',))
- self.customlist['state'] = 'normal'
- self.button_delete_custom.state(('!disabled',))
- def get_color(self):
- """Handle button to select a new color for the target tag.
- If a new color is selected while using a builtin theme, a
- name must be supplied to create a custom theme.
- Attributes accessed:
- highlight_target
- frame_color_set
- theme_source
- Attributes updated:
- color
- Methods:
- get_new_theme_name
- create_new
- """
- target = self.highlight_target.get()
- prev_color = self.style.lookup(self.frame_color_set['style'],
- 'background')
- rgbTuplet, color_string = colorchooser.askcolor(
- parent=self, title='Pick new color for : '+target,
- initialcolor=prev_color)
- if color_string and (color_string != prev_color):
- # User didn't cancel and they chose a new color.
- if self.theme_source.get(): # Current theme is a built-in.
- message = ('Your changes will be saved as a new Custom Theme. '
- 'Enter a name for your new Custom Theme below.')
- new_theme = self.get_new_theme_name(message)
- if not new_theme: # User cancelled custom theme creation.
- return
- else: # Create new custom theme based on previously active theme.
- self.create_new(new_theme)
- self.color.set(color_string)
- else: # Current theme is user defined.
- self.color.set(color_string)
- def on_new_color_set(self):
- "Display sample of new color selection on the dialog."
- new_color = self.color.get()
- self.style.configure('frame_color_set.TFrame', background=new_color)
- plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
- sample_element = self.theme_elements[self.highlight_target.get()][0]
- self.highlight_sample.tag_config(sample_element, **{plane: new_color})
- theme = self.custom_name.get()
- theme_element = sample_element + '-' + plane
- changes.add_option('highlight', theme, theme_element, new_color)
- def get_new_theme_name(self, message):
- "Return name of new theme from query popup."
- used_names = (idleConf.GetSectionList('user', 'highlight') +
- idleConf.GetSectionList('default', 'highlight'))
- new_theme = SectionName(
- self, 'New Custom Theme', message, used_names).result
- return new_theme
- def save_as_new_theme(self):
- """Prompt for new theme name and create the theme.
- Methods:
- get_new_theme_name
- create_new
- """
- new_theme_name = self.get_new_theme_name('New Theme Name:')
- if new_theme_name:
- self.create_new(new_theme_name)
- def create_new(self, new_theme_name):
- """Create a new custom theme with the given name.
- Create the new theme based on the previously active theme
- with the current changes applied. Once it is saved, then
- activate the new theme.
- Attributes accessed:
- builtin_name
- custom_name
- Attributes updated:
- customlist
- theme_source
- Method:
- save_new
- set_theme_type
- """
- if self.theme_source.get():
- theme_type = 'default'
- theme_name = self.builtin_name.get()
- else:
- theme_type = 'user'
- theme_name = self.custom_name.get()
- new_theme = idleConf.GetThemeDict(theme_type, theme_name)
- # Apply any of the old theme's unsaved changes to the new theme.
- if theme_name in changes['highlight']:
- theme_changes = changes['highlight'][theme_name]
- for element in theme_changes:
- new_theme[element] = theme_changes[element]
- # Save the new theme.
- self.save_new(new_theme_name, new_theme)
- # Change GUI over to the new theme.
- custom_theme_list = idleConf.GetSectionList('user', 'highlight')
- custom_theme_list.sort()
- self.customlist.SetMenu(custom_theme_list, new_theme_name)
- self.theme_source.set(0)
- self.set_theme_type()
- def set_highlight_target(self):
- """Set fg/bg toggle and color based on highlight tag target.
- Instance variables accessed:
- highlight_target
- Attributes updated:
- fg_on
- bg_on
- fg_bg_toggle
- Methods:
- set_color_sample
- Called from:
- var_changed_highlight_target
- load_theme_cfg
- """
- if self.highlight_target.get() == 'Cursor': # bg not possible
- self.fg_on.state(('disabled',))
- self.bg_on.state(('disabled',))
- self.fg_bg_toggle.set(1)
- else: # Both fg and bg can be set.
- self.fg_on.state(('!disabled',))
- self.bg_on.state(('!disabled',))
- self.fg_bg_toggle.set(1)
- self.set_color_sample()
- def set_color_sample_binding(self, *args):
- """Change color sample based on foreground/background toggle.
- Methods:
- set_color_sample
- """
- self.set_color_sample()
- def set_color_sample(self):
- """Set the color of the frame background to reflect the selected target.
- Instance variables accessed:
- theme_elements
- highlight_target
- fg_bg_toggle
- highlight_sample
- Attributes updated:
- frame_color_set
- """
- # Set the color sample area.
- tag = self.theme_elements[self.highlight_target.get()][0]
- plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
- color = self.highlight_sample.tag_cget(tag, plane)
- self.style.configure('frame_color_set.TFrame', background=color)
- def paint_theme_sample(self):
- """Apply the theme colors to each element tag in the sample text.
- Instance attributes accessed:
- theme_elements
- theme_source
- builtin_name
- custom_name
- Attributes updated:
- highlight_sample: Set the tag elements to the theme.
- Methods:
- set_color_sample
- Called from:
- var_changed_builtin_name
- var_changed_custom_name
- load_theme_cfg
- """
- if self.theme_source.get(): # Default theme
- theme = self.builtin_name.get()
- else: # User theme
- theme = self.custom_name.get()
- for element_title in self.theme_elements:
- element = self.theme_elements[element_title][0]
- colors = idleConf.GetHighlight(theme, element)
- if element == 'cursor': # Cursor sample needs special painting.
- colors['background'] = idleConf.GetHighlight(
- theme, 'normal')['background']
- # Handle any unsaved changes to this theme.
- if theme in changes['highlight']:
- theme_dict = changes['highlight'][theme]
- if element + '-foreground' in theme_dict:
- colors['foreground'] = theme_dict[element + '-foreground']
- if element + '-background' in theme_dict:
- colors['background'] = theme_dict[element + '-background']
- self.highlight_sample.tag_config(element, **colors)
- self.set_color_sample()
- def save_new(self, theme_name, theme):
- """Save a newly created theme to idleConf.
- theme_name - string, the name of the new theme
- theme - dictionary containing the new theme
- """
- idleConf.userCfg['highlight'].AddSection(theme_name)
- for element in theme:
- value = theme[element]
- idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
- def askyesno(self, *args, **kwargs):
- # Make testing easier. Could change implementation.
- return messagebox.askyesno(*args, **kwargs)
- def delete_custom(self):
- """Handle event to delete custom theme.
- The current theme is deactivated and the default theme is
- activated. The custom theme is permanently removed from
- the config file.
- Attributes accessed:
- custom_name
- Attributes updated:
- custom_theme_on
- customlist
- theme_source
- builtin_name
- Methods:
- deactivate_current_config
- save_all_changed_extensions
- activate_config_changes
- set_theme_type
- """
- theme_name = self.custom_name.get()
- delmsg = 'Are you sure you wish to delete the theme %r ?'
- if not self.askyesno(
- 'Delete Theme', delmsg % theme_name, parent=self):
- return
- self.cd.deactivate_current_config()
- # Remove theme from changes, config, and file.
- changes.delete_section('highlight', theme_name)
- # Reload user theme list.
- item_list = idleConf.GetSectionList('user', 'highlight')
- item_list.sort()
- if not item_list:
- self.custom_theme_on.state(('disabled',))
- self.customlist.SetMenu(item_list, '- no custom themes -')
- else:
- self.customlist.SetMenu(item_list, item_list[0])
- # Revert to default theme.
- self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
- self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
- # User can't back out of these changes, they must be applied now.
- changes.save_all()
- self.extpage.save_all_changed_extensions()
- self.cd.activate_config_changes()
- self.set_theme_type()
- class KeysPage(Frame):
- def __init__(self, master, extpage):
- super().__init__(master)
- self.extpage = extpage
- self.cd = master.winfo_toplevel()
- self.create_page_keys()
- self.load_key_cfg()
- def create_page_keys(self):
- """Return frame of widgets for Keys tab.
- Enable users to provisionally change both individual and sets of
- keybindings (shortcut keys). Except for features implemented as
- extensions, keybindings are stored in complete sets called
- keysets. Built-in keysets in idlelib/config-keys.def are fixed
- as far as the dialog is concerned. Any keyset can be used as the
- base for a new custom keyset, stored in .idlerc/config-keys.cfg.
- Function load_key_cfg() initializes tk variables and keyset
- lists and calls load_keys_list for the current keyset.
- Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
- keyset_source, which controls if the current set of keybindings
- are from a builtin or custom keyset. DynOptionMenus builtinlist
- and customlist contain lists of the builtin and custom keysets,
- respectively, and the current item from each list is stored in
- vars builtin_name and custom_name.
- Button delete_custom_keys invokes delete_custom_keys() to delete
- a custom keyset from idleConf.userCfg['keys'] and changes. Button
- save_custom_keys invokes save_as_new_key_set() which calls
- get_new_keys_name() and create_new_key_set() to save a custom keyset
- and its keybindings to idleConf.userCfg['keys'].
- Listbox bindingslist contains all of the keybindings for the
- selected keyset. The keybindings are loaded in load_keys_list()
- and are pairs of (event, [keys]) where keys can be a list
- of one or more key combinations to bind to the same event.
- Mouse button 1 click invokes on_bindingslist_select(), which
- allows button_new_keys to be clicked.
- So, an item is selected in listbindings, which activates
- button_new_keys, and clicking button_new_keys calls function
- get_new_keys(). Function get_new_keys() gets the key mappings from the
- current keyset for the binding event item that was selected. The
- function then displays another dialog, GetKeysDialog, with the
- selected binding event and current keys and allows new key sequences
- to be entered for that binding event. If the keys aren't
- changed, nothing happens. If the keys are changed and the keyset
- is a builtin, function get_new_keys_name() will be called
- for input of a custom keyset name. If no name is given, then the
- change to the keybinding will abort and no updates will be made. If
- a custom name is entered in the prompt or if the current keyset was
- already custom (and thus didn't require a prompt), then
- idleConf.userCfg['keys'] is updated in function create_new_key_set()
- with the change to the event binding. The item listing in bindingslist
- is updated with the new keys. Var keybinding is also set which invokes
- the callback function, var_changed_keybinding, to add the change to
- the 'keys' or 'extensions' changes tracker based on the binding type.
- Tk Variables:
- keybinding: Action/key bindings.
- Methods:
- load_keys_list: Reload active set.
- create_new_key_set: Combine active keyset and changes.
- set_keys_type: Command for keyset_source.
- save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
- deactivate_current_config: Remove keys bindings in editors.
- Widgets for KeysPage(frame): (*) widgets bound to self
- frame_key_sets: LabelFrame
- frames[0]: Frame
- (*)builtin_keyset_on: Radiobutton - var keyset_source
- (*)custom_keyset_on: Radiobutton - var keyset_source
- (*)builtinlist: DynOptionMenu - var builtin_name,
- func keybinding_selected
- (*)customlist: DynOptionMenu - var custom_name,
- func keybinding_selected
- (*)keys_message: Label
- frames[1]: Frame
- (*)button_delete_custom_keys: Button - delete_custom_keys
- (*)button_save_custom_keys: Button - save_as_new_key_set
- frame_custom: LabelFrame
- frame_target: Frame
- target_title: Label
- scroll_target_y: Scrollbar
- scroll_target_x: Scrollbar
- (*)bindingslist: ListBox - on_bindingslist_select
- (*)button_new_keys: Button - get_new_keys & ..._name
- """
- self.builtin_name = tracers.add(
- StringVar(self), self.var_changed_builtin_name)
- self.custom_name = tracers.add(
- StringVar(self), self.var_changed_custom_name)
- self.keyset_source = tracers.add(
- BooleanVar(self), self.var_changed_keyset_source)
- self.keybinding = tracers.add(
- StringVar(self), self.var_changed_keybinding)
- # Create widgets:
- # body and section frames.
- frame_custom = LabelFrame(
- self, borderwidth=2, relief=GROOVE,
- text=' Custom Key Bindings ')
- frame_key_sets = LabelFrame(
- self, borderwidth=2, relief=GROOVE, text=' Key Set ')
- # frame_custom.
- frame_target = Frame(frame_custom)
- target_title = Label(frame_target, text='Action - Key(s)')
- scroll_target_y = Scrollbar(frame_target)
- scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
- self.bindingslist = Listbox(
- frame_target, takefocus=FALSE, exportselection=FALSE)
- self.bindingslist.bind('<ButtonRelease-1>',
- self.on_bindingslist_select)
- scroll_target_y['command'] = self.bindingslist.yview
- scroll_target_x['command'] = self.bindingslist.xview
- self.bindingslist['yscrollcommand'] = scroll_target_y.set
- self.bindingslist['xscrollcommand'] = scroll_target_x.set
- self.button_new_keys = Button(
- frame_custom, text='Get New Keys for Selection',
- command=self.get_new_keys, state='disabled')
- # frame_key_sets.
- frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
- for i in range(2)]
- self.builtin_keyset_on = Radiobutton(
- frames[0], variable=self.keyset_source, value=1,
- command=self.set_keys_type, text='Use a Built-in Key Set')
- self.custom_keyset_on = Radiobutton(
- frames[0], variable=self.keyset_source, value=0,
- command=self.set_keys_type, text='Use a Custom Key Set')
- self.builtinlist = DynOptionMenu(
- frames[0], self.builtin_name, None, command=None)
- self.customlist = DynOptionMenu(
- frames[0], self.custom_name, None, command=None)
- self.button_delete_custom_keys = Button(
- frames[1], text='Delete Custom Key Set',
- command=self.delete_custom_keys)
- self.button_save_custom_keys = Button(
- frames[1], text='Save as New Custom Key Set',
- command=self.save_as_new_key_set)
- self.keys_message = Label(frames[0], borderwidth=2)
- # Pack widgets:
- # body.
- frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
- frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
- # frame_custom.
- self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
- frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
- # frame_target.
- frame_target.columnconfigure(0, weight=1)
- frame_target.rowconfigure(1, weight=1)
- target_title.grid(row=0, column=0, columnspan=2, sticky=W)
- self.bindingslist.grid(row=1, column=0, sticky=NSEW)
- scroll_target_y.grid(row=1, column=1, sticky=NS)
- scroll_target_x.grid(row=2, column=0, sticky=EW)
- # frame_key_sets.
- self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
- self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
- self.builtinlist.grid(row=0, column=1, sticky=NSEW)
- self.customlist.grid(row=1, column=1, sticky=NSEW)
- self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
- self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
- self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
- frames[0].pack(side=TOP, fill=BOTH, expand=True)
- frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
- def load_key_cfg(self):
- "Load current configuration settings for the keybinding options."
- # Set current keys type radiobutton.
- self.keyset_source.set(idleConf.GetOption(
- 'main', 'Keys', 'default', type='bool', default=1))
- # Set current keys.
- current_option = idleConf.CurrentKeys()
- # Load available keyset option menus.
- if self.keyset_source.get(): # Default theme selected.
- item_list = idleConf.GetSectionList('default', 'keys')
- item_list.sort()
- self.builtinlist.SetMenu(item_list, current_option)
- item_list = idleConf.GetSectionList('user', 'keys')
- item_list.sort()
- if not item_list:
- self.custom_keyset_on.state(('disabled',))
- self.custom_name.set('- no custom keys -')
- else:
- self.customlist.SetMenu(item_list, item_list[0])
- else: # User key set selected.
- item_list = idleConf.GetSectionList('user', 'keys')
- item_list.sort()
- self.customlist.SetMenu(item_list, current_option)
- item_list = idleConf.GetSectionList('default', 'keys')
- item_list.sort()
- self.builtinlist.SetMenu(item_list, idleConf.default_keys())
- self.set_keys_type()
- # Load keyset element list.
- keyset_name = idleConf.CurrentKeys()
- self.load_keys_list(keyset_name)
- def var_changed_builtin_name(self, *params):
- "Process selection of builtin key set."
- old_keys = (
- 'IDLE Classic Windows',
- 'IDLE Classic Unix',
- 'IDLE Classic Mac',
- 'IDLE Classic OSX',
- )
- value = self.builtin_name.get()
- if value not in old_keys:
- if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
- changes.add_option('main', 'Keys', 'name', old_keys[0])
- changes.add_option('main', 'Keys', 'name2', value)
- self.keys_message['text'] = 'New key set, see Help'
- else:
- changes.add_option('main', 'Keys', 'name', value)
- changes.add_option('main', 'Keys', 'name2', '')
- self.keys_message['text'] = ''
- self.load_keys_list(value)
- def var_changed_custom_name(self, *params):
- "Process selection of custom key set."
- value = self.custom_name.get()
- if value != '- no custom keys -':
- changes.add_option('main', 'Keys', 'name', value)
- self.load_keys_list(value)
- def var_changed_keyset_source(self, *params):
- "Process toggle between builtin key set and custom key set."
- value = self.keyset_source.get()
- changes.add_option('main', 'Keys', 'default', value)
- if value:
- self.var_changed_builtin_name()
- else:
- self.var_changed_custom_name()
- def var_changed_keybinding(self, *params):
- "Store change to a keybinding."
- value = self.keybinding.get()
- key_set = self.custom_name.get()
- event = self.bindingslist.get(ANCHOR).split()[0]
- if idleConf.IsCoreBinding(event):
- changes.add_option('keys', key_set, event, value)
- else: # Event is an extension binding.
- ext_name = idleConf.GetExtnNameForEvent(event)
- ext_keybind_section = ext_name + '_cfgBindings'
- changes.add_option('extensions', ext_keybind_section, event, value)
- def set_keys_type(self):
- "Set available screen options based on builtin or custom key set."
- if self.keyset_source.get():
- self.builtinlist['state'] = 'normal'
- self.customlist['state'] = 'disabled'
- self.button_delete_custom_keys.state(('disabled',))
- else:
- self.builtinlist['state'] = 'disabled'
- self.custom_keyset_on.state(('!disabled',))
- self.customlist['state'] = 'normal'
- self.button_delete_custom_keys.state(('!disabled',))
- def get_new_keys(self):
- """Handle event to change key binding for selected line.
- A selection of a key/binding in the list of current
- bindings pops up a dialog to enter a new binding. If
- the current key set is builtin and a binding has
- changed, then a name for a custom key set needs to be
- entered for the change to be applied.
- """
- list_index = self.bindingslist.index(ANCHOR)
- binding = self.bindingslist.get(list_index)
- bind_name = binding.split()[0]
- if self.keyset_source.get():
- current_key_set_name = self.builtin_name.get()
- else:
- current_key_set_name = self.custom_name.get()
- current_bindings = idleConf.GetCurrentKeySet()
- if current_key_set_name in changes['keys']: # unsaved changes
- key_set_changes = changes['keys'][current_key_set_name]
- for event in key_set_changes:
- current_bindings[event] = key_set_changes[event].split()
- current_key_sequences = list(current_bindings.values())
- new_keys = GetKeysWindow(self, 'Get New Keys', bind_name,
- current_key_sequences).result
- if new_keys:
- if self.keyset_source.get(): # Current key set is a built-in.
- message = ('Your changes will be saved as a new Custom Key Set.'
- ' Enter a name for your new Custom Key Set below.')
- new_keyset = self.get_new_keys_name(message)
- if not new_keyset: # User cancelled custom key set creation.
- self.bindingslist.select_set(list_index)
- self.bindingslist.select_anchor(list_index)
- return
- else: # Create new custom key set based on previously active key set.
- self.create_new_key_set(new_keyset)
- self.bindingslist.delete(list_index)
- self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
- self.bindingslist.select_set(list_index)
- self.bindingslist.select_anchor(list_index)
- self.keybinding.set(new_keys)
- else:
- self.bindingslist.select_set(list_index)
- self.bindingslist.select_anchor(list_index)
- def get_new_keys_name(self, message):
- "Return new key set name from query popup."
- used_names = (idleConf.GetSectionList('user', 'keys') +
- idleConf.GetSectionList('default', 'keys'))
- new_keyset = SectionName(
- self, 'New Custom Key Set', message, used_names).result
- return new_keyset
- def save_as_new_key_set(self):
- "Prompt for name of new key set and save changes using that name."
- new_keys_name = self.get_new_keys_name('New Key Set Name:')
- if new_keys_name:
- self.create_new_key_set(new_keys_name)
- def on_bindingslist_select(self, event):
- "Activate button to assign new keys to selected action."
- self.button_new_keys.state(('!disabled',))
- def create_new_key_set(self, new_key_set_name):
- """Create a new custom key set with the given name.
- Copy the bindings/keys from the previously active keyset
- to the new keyset and activate the new custom keyset.
- """
- if self.keyset_source.get():
- prev_key_set_name = self.builtin_name.get()
- else:
- prev_key_set_name = self.custom_name.get()
- prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
- new_keys = {}
- for event in prev_keys: # Add key set to changed items.
- event_name = event[2:-2] # Trim off the angle brackets.
- binding = ' '.join(prev_keys[event])
- new_keys[event_name] = binding
- # Handle any unsaved changes to prev key set.
- if prev_key_set_name in changes['keys']:
- key_set_changes = changes['keys'][prev_key_set_name]
- for event in key_set_changes:
- new_keys[event] = key_set_changes[event]
- # Save the new key set.
- self.save_new_key_set(new_key_set_name, new_keys)
- # Change GUI over to the new key set.
- custom_key_list = idleConf.GetSectionList('user', 'keys')
- custom_key_list.sort()
- self.customlist.SetMenu(custom_key_list, new_key_set_name)
- self.keyset_source.set(0)
- self.set_keys_type()
- def load_keys_list(self, keyset_name):
- """Reload the list of action/key binding pairs for the active key set.
- An action/key binding can be selected to change the key binding.
- """
- reselect = False
- if self.bindingslist.curselection():
- reselect = True
- list_index = self.bindingslist.index(ANCHOR)
- keyset = idleConf.GetKeySet(keyset_name)
- bind_names = list(keyset.keys())
- bind_names.sort()
- self.bindingslist.delete(0, END)
- for bind_name in bind_names:
- key = ' '.join(keyset[bind_name])
- bind_name = bind_name[2:-2] # Trim off the angle brackets.
- if keyset_name in changes['keys']:
- # Handle any unsaved changes to this key set.
- if bind_name in changes['keys'][keyset_name]:
- key = changes['keys'][keyset_name][bind_name]
- self.bindingslist.insert(END, bind_name+' - '+key)
- if reselect:
- self.bindingslist.see(list_index)
- self.bindingslist.select_set(list_index)
- self.bindingslist.select_anchor(list_index)
- @staticmethod
- def save_new_key_set(keyset_name, keyset):
- """Save a newly created core key set.
- Add keyset to idleConf.userCfg['keys'], not to disk.
- If the keyset doesn't exist, it is created. The
- binding/keys are taken from the keyset argument.
- keyset_name - string, the name of the new key set
- keyset - dictionary containing the new keybindings
- """
- idleConf.userCfg['keys'].AddSection(keyset_name)
- for event in keyset:
- value = keyset[event]
- idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
- def askyesno(self, *args, **kwargs):
- # Make testing easier. Could change implementation.
- return messagebox.askyesno(*args, **kwargs)
- def delete_custom_keys(self):
- """Handle event to delete a custom key set.
- Applying the delete deactivates the current configuration and
- reverts to the default. The custom key set is permanently
- deleted from the config file.
- """
- keyset_name = self.custom_name.get()
- delmsg = 'Are you sure you wish to delete the key set %r ?'
- if not self.askyesno(
- 'Delete Key Set', delmsg % keyset_name, parent=self):
- return
- self.cd.deactivate_current_config()
- # Remove key set from changes, config, and file.
- changes.delete_section('keys', keyset_name)
- # Reload user key set list.
- item_list = idleConf.GetSectionList('user', 'keys')
- item_list.sort()
- if not item_list:
- self.custom_keyset_on.state(('disabled',))
- self.customlist.SetMenu(item_list, '- no custom keys -')
- else:
- self.customlist.SetMenu(item_list, item_list[0])
- # Revert to default key set.
- self.keyset_source.set(idleConf.defaultCfg['main']
- .Get('Keys', 'default'))
- self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
- or idleConf.default_keys())
- # User can't back out of these changes, they must be applied now.
- changes.save_all()
- self.extpage.save_all_changed_extensions()
- self.cd.activate_config_changes()
- self.set_keys_type()
- class WinPage(Frame):
- def __init__(self, master):
- super().__init__(master)
- self.init_validators()
- self.create_page_windows()
- self.load_windows_cfg()
- def init_validators(self):
- digits_or_empty_re = re.compile(r'[0-9]*')
- def is_digits_or_empty(s):
- "Return 's is blank or contains only digits'"
- return digits_or_empty_re.fullmatch(s) is not None
- self.digits_only = (self.register(is_digits_or_empty), '%P',)
- def create_page_windows(self):
- """Return frame of widgets for Windows tab.
- Enable users to provisionally change general window options.
- Function load_windows_cfg initializes tk variable idleConf.
- Radiobuttons startup_shell_on and startup_editor_on set var
- startup_edit. Entry boxes win_width_int and win_height_int set var
- win_width and win_height. Setting var_name invokes the default
- callback that adds option to changes.
- Widgets for WinPage(Frame): > vars, bound to self
- frame_window: LabelFrame
- frame_run: Frame
- startup_title: Label
- startup_editor_on: Radiobutton > startup_edit
- startup_shell_on: Radiobutton > startup_edit
- frame_win_size: Frame
- win_size_title: Label
- win_width_title: Label
- win_width_int: Entry > win_width
- win_height_title: Label
- win_height_int: Entry > win_height
- frame_cursor: Frame
- indent_title: Label
- indent_chooser: Spinbox > indent_spaces
- blink_on: Checkbutton > cursor_blink
- frame_autocomplete: Frame
- auto_wait_title: Label
- auto_wait_int: Entry > autocomplete_wait
- frame_paren1: Frame
- paren_style_title: Label
- paren_style_type: OptionMenu > paren_style
- frame_paren2: Frame
- paren_time_title: Label
- paren_flash_time: Entry > flash_delay
- bell_on: Checkbutton > paren_bell
- frame_format: Frame
- format_width_title: Label
- format_width_int: Entry > format_width
- """
- # Integer values need StringVar because int('') raises.
- self.startup_edit = tracers.add(
- IntVar(self), ('main', 'General', 'editor-on-startup'))
- self.win_width = tracers.add(
- StringVar(self), ('main', 'EditorWindow', 'width'))
- self.win_height = tracers.add(
- StringVar(self), ('main', 'EditorWindow', 'height'))
- self.indent_spaces = tracers.add(
- StringVar(self), ('main', 'Indent', 'num-spaces'))
- self.cursor_blink = tracers.add(
- BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
- self.autocomplete_wait = tracers.add(
- StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
- self.paren_style = tracers.add(
- StringVar(self), ('extensions', 'ParenMatch', 'style'))
- self.flash_delay = tracers.add(
- StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
- self.paren_bell = tracers.add(
- BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
- self.format_width = tracers.add(
- StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
- # Create widgets:
- frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Window Preferences')
- frame_run = Frame(frame_window, borderwidth=0)
- startup_title = Label(frame_run, text='At Startup')
- self.startup_editor_on = Radiobutton(
- frame_run, variable=self.startup_edit, value=1,
- text="Open Edit Window")
- self.startup_shell_on = Radiobutton(
- frame_run, variable=self.startup_edit, value=0,
- text='Open Shell Window')
- frame_win_size = Frame(frame_window, borderwidth=0)
- win_size_title = Label(
- frame_win_size, text='Initial Window Size (in characters)')
- win_width_title = Label(frame_win_size, text='Width')
- self.win_width_int = Entry(
- frame_win_size, textvariable=self.win_width, width=3,
- validatecommand=self.digits_only, validate='key',
- )
- win_height_title = Label(frame_win_size, text='Height')
- self.win_height_int = Entry(
- frame_win_size, textvariable=self.win_height, width=3,
- validatecommand=self.digits_only, validate='key',
- )
- frame_cursor = Frame(frame_window, borderwidth=0)
- indent_title = Label(frame_cursor,
- text='Indent spaces (4 is standard)')
- try:
- self.indent_chooser = Spinbox(
- frame_cursor, textvariable=self.indent_spaces,
- from_=1, to=10, width=2,
- validatecommand=self.digits_only, validate='key')
- except TclError:
- self.indent_chooser = Combobox(
- frame_cursor, textvariable=self.indent_spaces,
- state="readonly", values=list(range(1,11)), width=3)
- cursor_blink_title = Label(frame_cursor, text='Cursor Blink')
- self.cursor_blink_bool = Checkbutton(frame_cursor, text="Cursor blink",
- variable=self.cursor_blink)
- frame_autocomplete = Frame(frame_window, borderwidth=0,)
- auto_wait_title = Label(frame_autocomplete,
- text='Completions Popup Wait (milliseconds)')
- self.auto_wait_int = Entry(
- frame_autocomplete, textvariable=self.autocomplete_wait,
- width=6, validatecommand=self.digits_only, validate='key')
- frame_paren1 = Frame(frame_window, borderwidth=0)
- paren_style_title = Label(frame_paren1, text='Paren Match Style')
- self.paren_style_type = OptionMenu(
- frame_paren1, self.paren_style, 'expression',
- "opener","parens","expression")
- frame_paren2 = Frame(frame_window, borderwidth=0)
- paren_time_title = Label(
- frame_paren2, text='Time Match Displayed (milliseconds)\n'
- '(0 is until next input)')
- self.paren_flash_time = Entry(
- frame_paren2, textvariable=self.flash_delay, width=6,
- validatecommand=self.digits_only, validate='key')
- self.bell_on = Checkbutton(
- frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
- frame_format = Frame(frame_window, borderwidth=0)
- format_width_title = Label(frame_format,
- text='Format Paragraph Max Width')
- self.format_width_int = Entry(
- frame_format, textvariable=self.format_width, width=4,
- validatecommand=self.digits_only, validate='key',
- )
- # Pack widgets:
- frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
- # frame_run.
- frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
- startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- # frame_win_size.
- frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
- win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
- win_height_title.pack(side=RIGHT, anchor=E, pady=5)
- self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
- win_width_title.pack(side=RIGHT, anchor=E, pady=5)
- # frame_cursor.
- frame_cursor.pack(side=TOP, padx=5, pady=0, fill=X)
- indent_title.pack(side=LEFT, anchor=W, padx=5)
- self.indent_chooser.pack(side=LEFT, anchor=W, padx=10)
- self.cursor_blink_bool.pack(side=RIGHT, anchor=E, padx=15, pady=5)
- # frame_autocomplete.
- frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
- auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
- # frame_paren.
- frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
- paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.paren_style_type.pack(side=TOP, padx=10, pady=5)
- frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
- paren_time_title.pack(side=LEFT, anchor=W, padx=5)
- self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
- self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
- # frame_format.
- frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
- format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.format_width_int.pack(side=TOP, padx=10, pady=5)
- def load_windows_cfg(self):
- # Set variables for all windows.
- self.startup_edit.set(idleConf.GetOption(
- 'main', 'General', 'editor-on-startup', type='bool'))
- self.win_width.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'width', type='int'))
- self.win_height.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'height', type='int'))
- self.indent_spaces.set(idleConf.GetOption(
- 'main', 'Indent', 'num-spaces', type='int'))
- self.cursor_blink.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'cursor-blink', type='bool'))
- self.autocomplete_wait.set(idleConf.GetOption(
- 'extensions', 'AutoComplete', 'popupwait', type='int'))
- self.paren_style.set(idleConf.GetOption(
- 'extensions', 'ParenMatch', 'style'))
- self.flash_delay.set(idleConf.GetOption(
- 'extensions', 'ParenMatch', 'flash-delay', type='int'))
- self.paren_bell.set(idleConf.GetOption(
- 'extensions', 'ParenMatch', 'bell'))
- self.format_width.set(idleConf.GetOption(
- 'extensions', 'FormatParagraph', 'max-width', type='int'))
- class ShedPage(Frame):
- def __init__(self, master):
- super().__init__(master)
- self.init_validators()
- self.create_page_shed()
- self.load_shelled_cfg()
- def init_validators(self):
- digits_or_empty_re = re.compile(r'[0-9]*')
- def is_digits_or_empty(s):
- "Return 's is blank or contains only digits'"
- return digits_or_empty_re.fullmatch(s) is not None
- self.digits_only = (self.register(is_digits_or_empty), '%P',)
- def create_page_shed(self):
- """Return frame of widgets for Shell/Ed tab.
- Enable users to provisionally change shell and editor options.
- Function load_shed_cfg initializes tk variables using idleConf.
- Entry box auto_squeeze_min_lines_int sets
- auto_squeeze_min_lines_int. Setting var_name invokes the
- default callback that adds option to changes.
- Widgets for ShedPage(Frame): (*) widgets bound to self
- frame_shell: LabelFrame
- frame_auto_squeeze_min_lines: Frame
- auto_squeeze_min_lines_title: Label
- (*)auto_squeeze_min_lines_int: Entry -
- auto_squeeze_min_lines
- frame_editor: LabelFrame
- frame_save: Frame
- run_save_title: Label
- (*)save_ask_on: Radiobutton - autosave
- (*)save_auto_on: Radiobutton - autosave
- frame_format: Frame
- format_width_title: Label
- (*)format_width_int: Entry - format_width
- frame_line_numbers_default: Frame
- line_numbers_default_title: Label
- (*)line_numbers_default_bool: Checkbutton - line_numbers_default
- frame_context: Frame
- context_title: Label
- (*)context_int: Entry - context_lines
- """
- # Integer values need StringVar because int('') raises.
- self.auto_squeeze_min_lines = tracers.add(
- StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
- self.autosave = tracers.add(
- IntVar(self), ('main', 'General', 'autosave'))
- self.line_numbers_default = tracers.add(
- BooleanVar(self),
- ('main', 'EditorWindow', 'line-numbers-default'))
- self.context_lines = tracers.add(
- StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
- # Create widgets:
- frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Shell Preferences')
- frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Editor Preferences')
- # Frame_shell.
- frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
- auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
- text='Auto-Squeeze Min. Lines:')
- self.auto_squeeze_min_lines_int = Entry(
- frame_auto_squeeze_min_lines, width=4,
- textvariable=self.auto_squeeze_min_lines,
- validatecommand=self.digits_only, validate='key',
- )
- # Frame_editor.
- frame_save = Frame(frame_editor, borderwidth=0)
- run_save_title = Label(frame_save, text='At Start of Run (F5) ')
- self.save_ask_on = Radiobutton(
- frame_save, variable=self.autosave, value=0,
- text="Prompt to Save")
- self.save_auto_on = Radiobutton(
- frame_save, variable=self.autosave, value=1,
- text='No Prompt')
- frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
- line_numbers_default_title = Label(
- frame_line_numbers_default, text='Show line numbers in new windows')
- self.line_numbers_default_bool = Checkbutton(
- frame_line_numbers_default,
- variable=self.line_numbers_default,
- width=1)
- frame_context = Frame(frame_editor, borderwidth=0)
- context_title = Label(frame_context, text='Max Context Lines :')
- self.context_int = Entry(
- frame_context, textvariable=self.context_lines, width=3,
- validatecommand=self.digits_only, validate='key',
- )
- # Pack widgets:
- frame_shell.pack(side=TOP, padx=5, pady=5, fill=BOTH)
- Label(self).pack() # Spacer -- better solution?
- frame_editor.pack(side=TOP, padx=5, pady=5, fill=BOTH)
- # frame_auto_squeeze_min_lines
- frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
- auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
- # frame_save.
- frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
- run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- # frame_line_numbers_default.
- frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
- line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
- # frame_context.
- frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
- context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.context_int.pack(side=TOP, padx=5, pady=5)
- def load_shelled_cfg(self):
- # Set variables for shell windows.
- self.auto_squeeze_min_lines.set(idleConf.GetOption(
- 'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
- # Set variables for editor windows.
- self.autosave.set(idleConf.GetOption(
- 'main', 'General', 'autosave', default=0, type='bool'))
- self.line_numbers_default.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'line-numbers-default', type='bool'))
- self.context_lines.set(idleConf.GetOption(
- 'extensions', 'CodeContext', 'maxlines', type='int'))
- class ExtPage(Frame):
- def __init__(self, master):
- super().__init__(master)
- self.ext_defaultCfg = idleConf.defaultCfg['extensions']
- self.ext_userCfg = idleConf.userCfg['extensions']
- self.is_int = self.register(is_int)
- self.load_extensions()
- self.create_page_extensions() # Requires extension names.
- def create_page_extensions(self):
- """Configure IDLE feature extensions and help menu extensions.
- List the feature extensions and a configuration box for the
- selected extension. Help menu extensions are in a HelpFrame.
- This code reads the current configuration using idleConf,
- supplies a GUI interface to change the configuration values,
- and saves the changes using idleConf.
- Some changes may require restarting IDLE. This depends on each
- extension's implementation.
- All values are treated as text, and it is up to the user to
- supply reasonable values. The only exception to this are the
- 'enable*' options, which are boolean, and can be toggled with a
- True/False button.
- Methods:
- extension_selected: Handle selection from list.
- create_extension_frame: Hold widgets for one extension.
- set_extension_value: Set in userCfg['extensions'].
- save_all_changed_extensions: Call extension page Save().
- """
- self.extension_names = StringVar(self)
- frame_ext = LabelFrame(self, borderwidth=2, relief=GROOVE,
- text=' Feature Extensions ')
- self.frame_help = HelpFrame(self, borderwidth=2, relief=GROOVE,
- text=' Help Menu Extensions ')
- frame_ext.rowconfigure(0, weight=1)
- frame_ext.columnconfigure(2, weight=1)
- self.extension_list = Listbox(frame_ext, listvariable=self.extension_names,
- selectmode='browse')
- self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
- scroll = Scrollbar(frame_ext, command=self.extension_list.yview)
- self.extension_list.yscrollcommand=scroll.set
- self.details_frame = LabelFrame(frame_ext, width=250, height=250)
- self.extension_list.grid(column=0, row=0, sticky='nws')
- scroll.grid(column=1, row=0, sticky='ns')
- self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
- frame_ext.configure(padding=10)
- self.config_frame = {}
- self.current_extension = None
- self.outerframe = self # TEMPORARY
- self.tabbed_page_set = self.extension_list # TEMPORARY
- # Create the frame holding controls for each extension.
- ext_names = ''
- for ext_name in sorted(self.extensions):
- self.create_extension_frame(ext_name)
- ext_names = ext_names + '{' + ext_name + '} '
- self.extension_names.set(ext_names)
- self.extension_list.selection_set(0)
- self.extension_selected(None)
- frame_ext.grid(row=0, column=0, sticky='nsew')
- Label(self).grid(row=1, column=0) # Spacer. Replace with config?
- self.frame_help.grid(row=2, column=0, sticky='sew')
- def load_extensions(self):
- "Fill self.extensions with data from the default and user configs."
- self.extensions = {}
- for ext_name in idleConf.GetExtensions(active_only=False):
- # Former built-in extensions are already filtered out.
- self.extensions[ext_name] = []
- for ext_name in self.extensions:
- opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
- # Bring 'enable' options to the beginning of the list.
- enables = [opt_name for opt_name in opt_list
- if opt_name.startswith('enable')]
- for opt_name in enables:
- opt_list.remove(opt_name)
- opt_list = enables + opt_list
- for opt_name in opt_list:
- def_str = self.ext_defaultCfg.Get(
- ext_name, opt_name, raw=True)
- try:
- def_obj = {'True':True, 'False':False}[def_str]
- opt_type = 'bool'
- except KeyError:
- try:
- def_obj = int(def_str)
- opt_type = 'int'
- except ValueError:
- def_obj = def_str
- opt_type = None
- try:
- value = self.ext_userCfg.Get(
- ext_name, opt_name, type=opt_type, raw=True,
- default=def_obj)
- except ValueError: # Need this until .Get fixed.
- value = def_obj # Bad values overwritten by entry.
- var = StringVar(self)
- var.set(str(value))
- self.extensions[ext_name].append({'name': opt_name,
- 'type': opt_type,
- 'default': def_str,
- 'value': value,
- 'var': var,
- })
- def extension_selected(self, event):
- "Handle selection of an extension from the list."
- newsel = self.extension_list.curselection()
- if newsel:
- newsel = self.extension_list.get(newsel)
- if newsel is None or newsel != self.current_extension:
- if self.current_extension:
- self.details_frame.config(text='')
- self.config_frame[self.current_extension].grid_forget()
- self.current_extension = None
- if newsel:
- self.details_frame.config(text=newsel)
- self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
- self.current_extension = newsel
- def create_extension_frame(self, ext_name):
- """Create a frame holding the widgets to configure one extension"""
- f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
- self.config_frame[ext_name] = f
- entry_area = f.interior
- # Create an entry for each configuration option.
- for row, opt in enumerate(self.extensions[ext_name]):
- # Create a row with a label and entry/checkbutton.
- label = Label(entry_area, text=opt['name'])
- label.grid(row=row, column=0, sticky=NW)
- var = opt['var']
- if opt['type'] == 'bool':
- Checkbutton(entry_area, variable=var,
- onvalue='True', offvalue='False', width=8
- ).grid(row=row, column=1, sticky=W, padx=7)
- elif opt['type'] == 'int':
- Entry(entry_area, textvariable=var, validate='key',
- validatecommand=(self.is_int, '%P'), width=10
- ).grid(row=row, column=1, sticky=NSEW, padx=7)
- else: # type == 'str'
- # Limit size to fit non-expanding space with larger font.
- Entry(entry_area, textvariable=var, width=15
- ).grid(row=row, column=1, sticky=NSEW, padx=7)
- return
- def set_extension_value(self, section, opt):
- """Return True if the configuration was added or changed.
- If the value is the same as the default, then remove it
- from user config file.
- """
- name = opt['name']
- default = opt['default']
- value = opt['var'].get().strip() or default
- opt['var'].set(value)
- # if self.defaultCfg.has_section(section):
- # Currently, always true; if not, indent to return.
- if (value == default):
- return self.ext_userCfg.RemoveOption(section, name)
- # Set the option.
- return self.ext_userCfg.SetOption(section, name, value)
- def save_all_changed_extensions(self):
- """Save configuration changes to the user config file.
- Attributes accessed:
- extensions
- Methods:
- set_extension_value
- """
- has_changes = False
- for ext_name in self.extensions:
- options = self.extensions[ext_name]
- for opt in options:
- if self.set_extension_value(ext_name, opt):
- has_changes = True
- if has_changes:
- self.ext_userCfg.Save()
- class HelpFrame(LabelFrame):
- def __init__(self, master, **cfg):
- super().__init__(master, **cfg)
- self.create_frame_help()
- self.load_helplist()
- def create_frame_help(self):
- """Create LabelFrame for additional help menu sources.
- load_helplist loads list user_helplist with
- name, position pairs and copies names to listbox helplist.
- Clicking a name invokes help_source selected. Clicking
- button_helplist_name invokes helplist_item_name, which also
- changes user_helplist. These functions all call
- set_add_delete_state. All but load call update_help_changes to
- rewrite changes['main']['HelpFiles'].
- Widgets for HelpFrame(LabelFrame): (*) widgets bound to self
- frame_helplist: Frame
- (*)helplist: ListBox
- scroll_helplist: Scrollbar
- frame_buttons: Frame
- (*)button_helplist_edit
- (*)button_helplist_add
- (*)button_helplist_remove
- """
- # self = frame_help in dialog (until ExtPage class).
- frame_helplist = Frame(self)
- self.helplist = Listbox(
- frame_helplist, height=5, takefocus=True,
- exportselection=FALSE)
- scroll_helplist = Scrollbar(frame_helplist)
- scroll_helplist['command'] = self.helplist.yview
- self.helplist['yscrollcommand'] = scroll_helplist.set
- self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
- frame_buttons = Frame(self)
- self.button_helplist_edit = Button(
- frame_buttons, text='Edit', state='disabled',
- width=8, command=self.helplist_item_edit)
- self.button_helplist_add = Button(
- frame_buttons, text='Add',
- width=8, command=self.helplist_item_add)
- self.button_helplist_remove = Button(
- frame_buttons, text='Remove', state='disabled',
- width=8, command=self.helplist_item_remove)
- # Pack frame_help.
- frame_helplist.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
- self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
- scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
- frame_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
- self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
- self.button_helplist_add.pack(side=TOP, anchor=W)
- self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
- def help_source_selected(self, event):
- "Handle event for selecting additional help."
- self.set_add_delete_state()
- def set_add_delete_state(self):
- "Toggle the state for the help list buttons based on list entries."
- if self.helplist.size() < 1: # No entries in list.
- self.button_helplist_edit.state(('disabled',))
- self.button_helplist_remove.state(('disabled',))
- else: # Some entries.
- if self.helplist.curselection(): # There currently is a selection.
- self.button_helplist_edit.state(('!disabled',))
- self.button_helplist_remove.state(('!disabled',))
- else: # There currently is not a selection.
- self.button_helplist_edit.state(('disabled',))
- self.button_helplist_remove.state(('disabled',))
- def helplist_item_add(self):
- """Handle add button for the help list.
- Query for name and location of new help sources and add
- them to the list.
- """
- help_source = HelpSource(self, 'New Help Source').result
- if help_source:
- self.user_helplist.append(help_source)
- self.helplist.insert(END, help_source[0])
- self.update_help_changes()
- def helplist_item_edit(self):
- """Handle edit button for the help list.
- Query with existing help source information and update
- config if the values are changed.
- """
- item_index = self.helplist.index(ANCHOR)
- help_source = self.user_helplist[item_index]
- new_help_source = HelpSource(
- self, 'Edit Help Source',
- menuitem=help_source[0],
- filepath=help_source[1],
- ).result
- if new_help_source and new_help_source != help_source:
- self.user_helplist[item_index] = new_help_source
- self.helplist.delete(item_index)
- self.helplist.insert(item_index, new_help_source[0])
- self.update_help_changes()
- self.set_add_delete_state() # Selected will be un-selected
- def helplist_item_remove(self):
- """Handle remove button for the help list.
- Delete the help list item from config.
- """
- item_index = self.helplist.index(ANCHOR)
- del(self.user_helplist[item_index])
- self.helplist.delete(item_index)
- self.update_help_changes()
- self.set_add_delete_state()
- def update_help_changes(self):
- "Clear and rebuild the HelpFiles section in changes"
- changes['main']['HelpFiles'] = {}
- for num in range(1, len(self.user_helplist) + 1):
- changes.add_option(
- 'main', 'HelpFiles', str(num),
- ';'.join(self.user_helplist[num-1][:2]))
- def load_helplist(self):
- # Set additional help sources.
- self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
- self.helplist.delete(0, 'end')
- for help_item in self.user_helplist:
- self.helplist.insert(END, help_item[0])
- self.set_add_delete_state()
- class VarTrace:
- """Maintain Tk variables trace state."""
- def __init__(self):
- """Store Tk variables and callbacks.
- untraced: List of tuples (var, callback)
- that do not have the callback attached
- to the Tk var.
- traced: List of tuples (var, callback) where
- that callback has been attached to the var.
- """
- self.untraced = []
- self.traced = []
- def clear(self):
- "Clear lists (for tests)."
- # Call after all tests in a module to avoid memory leaks.
- self.untraced.clear()
- self.traced.clear()
- def add(self, var, callback):
- """Add (var, callback) tuple to untraced list.
- Args:
- var: Tk variable instance.
- callback: Either function name to be used as a callback
- or a tuple with IdleConf config-type, section, and
- option names used in the default callback.
- Return:
- Tk variable instance.
- """
- if isinstance(callback, tuple):
- callback = self.make_callback(var, callback)
- self.untraced.append((var, callback))
- return var
- @staticmethod
- def make_callback(var, config):
- "Return default callback function to add values to changes instance."
- def default_callback(*params):
- "Add config values to changes instance."
- changes.add_option(*config, var.get())
- return default_callback
- def attach(self):
- "Attach callback to all vars that are not traced."
- while self.untraced:
- var, callback = self.untraced.pop()
- var.trace_add('write', callback)
- self.traced.append((var, callback))
- def detach(self):
- "Remove callback from traced vars."
- while self.traced:
- var, callback = self.traced.pop()
- var.trace_remove('write', var.trace_info()[0][1])
- self.untraced.append((var, callback))
- tracers = VarTrace()
- help_common = '''\
- When you click either the Apply or Ok buttons, settings in this
- dialog that are different from IDLE's default are saved in
- a .idlerc directory in your home directory. Except as noted,
- these changes apply to all versions of IDLE installed on this
- machine. [Cancel] only cancels changes made since the last save.
- '''
- help_pages = {
- 'Fonts/Tabs':'''
- Font sample: This shows what a selection of Basic Multilingual Plane
- unicode characters look like for the current font selection. If the
- selected font does not define a character, Tk attempts to find another
- font that does. Substitute glyphs depend on what is available on a
- particular system and will not necessarily have the same size as the
- font selected. Line contains 20 characters up to Devanagari, 14 for
- Tamil, and 10 for East Asia.
- Hebrew and Arabic letters should display right to left, starting with
- alef, \u05d0 and \u0627. Arabic digits display left to right. The
- Devanagari and Tamil lines start with digits. The East Asian lines
- are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
- Hiragana and Katakana.
- You can edit the font sample. Changes remain until IDLE is closed.
- ''',
- 'Highlights': '''
- Highlighting:
- The IDLE Dark color theme is new in October 2015. It can only
- be used with older IDLE releases if it is saved as a custom
- theme, with a different name.
- ''',
- 'Keys': '''
- Keys:
- The IDLE Modern Unix key set is new in June 2016. It can only
- be used with older IDLE releases if it is saved as a custom
- key set, with a different name.
- ''',
- 'General': '''
- General:
- AutoComplete: Popupwait is milliseconds to wait after key char, without
- cursor movement, before popping up completion box. Key char is '.' after
- identifier or a '/' (or '\\' on Windows) within a string.
- FormatParagraph: Max-width is max chars in lines after re-formatting.
- Use with paragraphs in both strings and comment blocks.
- ParenMatch: Style indicates what is highlighted when closer is entered:
- 'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
- 'expression' (default) - also everything in between. Flash-delay is how
- long to highlight if cursor is not moved (0 means forever).
- CodeContext: Maxlines is the maximum number of code context lines to
- display when Code Context is turned on for an editor window.
- Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
- of output to automatically "squeeze".
- ''',
- 'Extensions': '''
- ZzDummy: This extension is provided as an example for how to create and
- use an extension. Enable indicates whether the extension is active or
- not; likewise enable_editor and enable_shell indicate which windows it
- will be active on. For this extension, z-text is the text that will be
- inserted at or removed from the beginning of the lines of selected text,
- or the current line if no selection.
- ''',
- }
- def is_int(s):
- "Return 's is blank or represents an int'"
- if not s:
- return True
- try:
- int(s)
- return True
- except ValueError:
- return False
- class VerticalScrolledFrame(Frame):
- """A pure Tkinter vertically scrollable frame.
- * Use the 'interior' attribute to place widgets inside the scrollable frame
- * Construct and pack/place/grid normally
- * This frame only allows vertical scrolling
- """
- def __init__(self, parent, *args, **kw):
- Frame.__init__(self, parent, *args, **kw)
- # Create a canvas object and a vertical scrollbar for scrolling it.
- vscrollbar = Scrollbar(self, orient=VERTICAL)
- vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
- canvas = Canvas(self, borderwidth=0, highlightthickness=0,
- yscrollcommand=vscrollbar.set, width=240)
- canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
- vscrollbar.config(command=canvas.yview)
- # Reset the view.
- canvas.xview_moveto(0)
- canvas.yview_moveto(0)
- # Create a frame inside the canvas which will be scrolled with it.
- self.interior = interior = Frame(canvas)
- interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
- # Track changes to the canvas and frame width and sync them,
- # also updating the scrollbar.
- def _configure_interior(event):
- # Update the scrollbars to match the size of the inner frame.
- size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
- canvas.config(scrollregion="0 0 %s %s" % size)
- interior.bind('<Configure>', _configure_interior)
- def _configure_canvas(event):
- if interior.winfo_reqwidth() != canvas.winfo_width():
- # Update the inner frame's width to fill the canvas.
- canvas.itemconfigure(interior_id, width=canvas.winfo_width())
- canvas.bind('<Configure>', _configure_canvas)
- return
- if __name__ == '__main__':
- from unittest import main
- main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
- from idlelib.idle_test.htest import run
- run(ConfigDialog)
|