ยปCore Development>Code coverage>Lib/idlelib/configdialog.py

Python code coverage for Lib/idlelib/configdialog.py

#countcontent
1n/a"""IDLE Configuration Dialog: support user customization of IDLE by GUI
2n/a
3n/aCustomize font faces, sizes, and colorization attributes. Set indentation
4n/adefaults. Customize keybindings. Colorization and keybindings can be
5n/asaved as user defined sets. Select startup options including shell/editor
6n/aand default window size. Define additional help sources.
7n/a
8n/aNote that tab width in IDLE is currently fixed at eight due to Tk issues.
9n/aRefer to comments in EditorWindow autoindent code for details.
10n/a
11n/a"""
12n/afrom tkinter import *
13n/afrom tkinter.ttk import Scrollbar
14n/aimport tkinter.colorchooser as tkColorChooser
15n/aimport tkinter.font as tkFont
16n/aimport tkinter.messagebox as tkMessageBox
17n/a
18n/afrom idlelib.config import idleConf
19n/afrom idlelib.config_key import GetKeysDialog
20n/afrom idlelib.dynoption import DynOptionMenu
21n/afrom idlelib import macosx
22n/afrom idlelib.query import SectionName, HelpSource
23n/afrom idlelib.tabbedpages import TabbedPageSet
24n/afrom idlelib.textview import view_text
25n/a
26n/aclass ConfigDialog(Toplevel):
27n/a
28n/a def __init__(self, parent, title='', _htest=False, _utest=False):
29n/a """
30n/a _htest - bool, change box location when running htest
31n/a _utest - bool, don't wait_window when running unittest
32n/a """
33n/a Toplevel.__init__(self, parent)
34n/a self.parent = parent
35n/a if _htest:
36n/a parent.instance_dict = {}
37n/a self.wm_withdraw()
38n/a
39n/a self.configure(borderwidth=5)
40n/a self.title(title or 'IDLE Preferences')
41n/a self.geometry(
42n/a "+%d+%d" % (parent.winfo_rootx() + 20,
43n/a parent.winfo_rooty() + (30 if not _htest else 150)))
44n/a #Theme Elements. Each theme element key is its display name.
45n/a #The first value of the tuple is the sample area tag name.
46n/a #The second value is the display name list sort index.
47n/a self.themeElements={
48n/a 'Normal Text': ('normal', '00'),
49n/a 'Python Keywords': ('keyword', '01'),
50n/a 'Python Definitions': ('definition', '02'),
51n/a 'Python Builtins': ('builtin', '03'),
52n/a 'Python Comments': ('comment', '04'),
53n/a 'Python Strings': ('string', '05'),
54n/a 'Selected Text': ('hilite', '06'),
55n/a 'Found Text': ('hit', '07'),
56n/a 'Cursor': ('cursor', '08'),
57n/a 'Editor Breakpoint': ('break', '09'),
58n/a 'Shell Normal Text': ('console', '10'),
59n/a 'Shell Error Text': ('error', '11'),
60n/a 'Shell Stdout Text': ('stdout', '12'),
61n/a 'Shell Stderr Text': ('stderr', '13'),
62n/a }
63n/a self.ResetChangedItems() #load initial values in changed items dict
64n/a self.CreateWidgets()
65n/a self.resizable(height=FALSE, width=FALSE)
66n/a self.transient(parent)
67n/a self.grab_set()
68n/a self.protocol("WM_DELETE_WINDOW", self.Cancel)
69n/a self.tabPages.focus_set()
70n/a #key bindings for this dialog
71n/a #self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
72n/a #self.bind('<Alt-a>', self.Apply) #apply changes, save
73n/a #self.bind('<F1>', self.Help) #context help
74n/a self.LoadConfigs()
75n/a self.AttachVarCallbacks() #avoid callbacks during LoadConfigs
76n/a
77n/a if not _utest:
78n/a self.wm_deiconify()
79n/a self.wait_window()
80n/a
81n/a def CreateWidgets(self):
82n/a self.tabPages = TabbedPageSet(self,
83n/a page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
84n/a 'Extensions'])
85n/a self.tabPages.pack(side=TOP, expand=TRUE, fill=BOTH)
86n/a self.CreatePageFontTab()
87n/a self.CreatePageHighlight()
88n/a self.CreatePageKeys()
89n/a self.CreatePageGeneral()
90n/a self.CreatePageExtensions()
91n/a self.create_action_buttons().pack(side=BOTTOM)
92n/a
93n/a def create_action_buttons(self):
94n/a if macosx.isAquaTk():
95n/a # Changing the default padding on OSX results in unreadable
96n/a # text in the buttons
97n/a paddingArgs = {}
98n/a else:
99n/a paddingArgs = {'padx':6, 'pady':3}
100n/a outer = Frame(self, pady=2)
101n/a buttons = Frame(outer, pady=2)
102n/a for txt, cmd in (
103n/a ('Ok', self.Ok),
104n/a ('Apply', self.Apply),
105n/a ('Cancel', self.Cancel),
106n/a ('Help', self.Help)):
107n/a Button(buttons, text=txt, command=cmd, takefocus=FALSE,
108n/a **paddingArgs).pack(side=LEFT, padx=5)
109n/a # add space above buttons
110n/a Frame(outer, height=2, borderwidth=0).pack(side=TOP)
111n/a buttons.pack(side=BOTTOM)
112n/a return outer
113n/a
114n/a def CreatePageFontTab(self):
115n/a parent = self.parent
116n/a self.fontSize = StringVar(parent)
117n/a self.fontBold = BooleanVar(parent)
118n/a self.fontName = StringVar(parent)
119n/a self.spaceNum = IntVar(parent)
120n/a self.editFont = tkFont.Font(parent, ('courier', 10, 'normal'))
121n/a
122n/a ##widget creation
123n/a #body frame
124n/a frame = self.tabPages.pages['Fonts/Tabs'].frame
125n/a #body section frames
126n/a frameFont = LabelFrame(
127n/a frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
128n/a frameIndent = LabelFrame(
129n/a frame, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
130n/a #frameFont
131n/a frameFontName = Frame(frameFont)
132n/a frameFontParam = Frame(frameFont)
133n/a labelFontNameTitle = Label(
134n/a frameFontName, justify=LEFT, text='Font Face :')
135n/a self.listFontName = Listbox(
136n/a frameFontName, height=5, takefocus=FALSE, exportselection=FALSE)
137n/a self.listFontName.bind(
138n/a '<ButtonRelease-1>', self.OnListFontButtonRelease)
139n/a scrollFont = Scrollbar(frameFontName)
140n/a scrollFont.config(command=self.listFontName.yview)
141n/a self.listFontName.config(yscrollcommand=scrollFont.set)
142n/a labelFontSizeTitle = Label(frameFontParam, text='Size :')
143n/a self.optMenuFontSize = DynOptionMenu(
144n/a frameFontParam, self.fontSize, None, command=self.SetFontSample)
145n/a checkFontBold = Checkbutton(
146n/a frameFontParam, variable=self.fontBold, onvalue=1,
147n/a offvalue=0, text='Bold', command=self.SetFontSample)
148n/a frameFontSample = Frame(frameFont, relief=SOLID, borderwidth=1)
149n/a self.labelFontSample = Label(
150n/a frameFontSample, justify=LEFT, font=self.editFont,
151n/a text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
152n/a #frameIndent
153n/a frameIndentSize = Frame(frameIndent)
154n/a labelSpaceNumTitle = Label(
155n/a frameIndentSize, justify=LEFT,
156n/a text='Python Standard: 4 Spaces!')
157n/a self.scaleSpaceNum = Scale(
158n/a frameIndentSize, variable=self.spaceNum,
159n/a orient='horizontal', tickinterval=2, from_=2, to=16)
160n/a
161n/a #widget packing
162n/a #body
163n/a frameFont.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
164n/a frameIndent.pack(side=LEFT, padx=5, pady=5, fill=Y)
165n/a #frameFont
166n/a frameFontName.pack(side=TOP, padx=5, pady=5, fill=X)
167n/a frameFontParam.pack(side=TOP, padx=5, pady=5, fill=X)
168n/a labelFontNameTitle.pack(side=TOP, anchor=W)
169n/a self.listFontName.pack(side=LEFT, expand=TRUE, fill=X)
170n/a scrollFont.pack(side=LEFT, fill=Y)
171n/a labelFontSizeTitle.pack(side=LEFT, anchor=W)
172n/a self.optMenuFontSize.pack(side=LEFT, anchor=W)
173n/a checkFontBold.pack(side=LEFT, anchor=W, padx=20)
174n/a frameFontSample.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
175n/a self.labelFontSample.pack(expand=TRUE, fill=BOTH)
176n/a #frameIndent
177n/a frameIndentSize.pack(side=TOP, fill=X)
178n/a labelSpaceNumTitle.pack(side=TOP, anchor=W, padx=5)
179n/a self.scaleSpaceNum.pack(side=TOP, padx=5, fill=X)
180n/a return frame
181n/a
182n/a def CreatePageHighlight(self):
183n/a parent = self.parent
184n/a self.builtinTheme = StringVar(parent)
185n/a self.customTheme = StringVar(parent)
186n/a self.fgHilite = BooleanVar(parent)
187n/a self.colour = StringVar(parent)
188n/a self.fontName = StringVar(parent)
189n/a self.themeIsBuiltin = BooleanVar(parent)
190n/a self.highlightTarget = StringVar(parent)
191n/a
192n/a ##widget creation
193n/a #body frame
194n/a frame = self.tabPages.pages['Highlighting'].frame
195n/a #body section frames
196n/a frameCustom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
197n/a text=' Custom Highlighting ')
198n/a frameTheme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
199n/a text=' Highlighting Theme ')
200n/a #frameCustom
201n/a self.textHighlightSample=Text(
202n/a frameCustom, relief=SOLID, borderwidth=1,
203n/a font=('courier', 12, ''), cursor='hand2', width=21, height=11,
204n/a takefocus=FALSE, highlightthickness=0, wrap=NONE)
205n/a text=self.textHighlightSample
206n/a text.bind('<Double-Button-1>', lambda e: 'break')
207n/a text.bind('<B1-Motion>', lambda e: 'break')
208n/a textAndTags=(
209n/a ('#you can click here', 'comment'), ('\n', 'normal'),
210n/a ('#to choose items', 'comment'), ('\n', 'normal'),
211n/a ('def', 'keyword'), (' ', 'normal'),
212n/a ('func', 'definition'), ('(param):\n ', 'normal'),
213n/a ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
214n/a ("'string'", 'string'), ('\n var1 = ', 'normal'),
215n/a ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
216n/a ("'found'", 'hit'), ('\n var3 = ', 'normal'),
217n/a ('list', 'builtin'), ('(', 'normal'),
218n/a ('None', 'keyword'), (')\n', 'normal'),
219n/a (' breakpoint("line")', 'break'), ('\n\n', 'normal'),
220n/a (' error ', 'error'), (' ', 'normal'),
221n/a ('cursor |', 'cursor'), ('\n ', 'normal'),
222n/a ('shell', 'console'), (' ', 'normal'),
223n/a ('stdout', 'stdout'), (' ', 'normal'),
224n/a ('stderr', 'stderr'), ('\n', 'normal'))
225n/a for txTa in textAndTags:
226n/a text.insert(END, txTa[0], txTa[1])
227n/a for element in self.themeElements:
228n/a def tem(event, elem=element):
229n/a event.widget.winfo_toplevel().highlightTarget.set(elem)
230n/a text.tag_bind(
231n/a self.themeElements[element][0], '<ButtonPress-1>', tem)
232n/a text.config(state=DISABLED)
233n/a self.frameColourSet = Frame(frameCustom, relief=SOLID, borderwidth=1)
234n/a frameFgBg = Frame(frameCustom)
235n/a buttonSetColour = Button(
236n/a self.frameColourSet, text='Choose Colour for :',
237n/a command=self.GetColour, highlightthickness=0)
238n/a self.optMenuHighlightTarget = DynOptionMenu(
239n/a self.frameColourSet, self.highlightTarget, None,
240n/a highlightthickness=0) #, command=self.SetHighlightTargetBinding
241n/a self.radioFg = Radiobutton(
242n/a frameFgBg, variable=self.fgHilite, value=1,
243n/a text='Foreground', command=self.SetColourSampleBinding)
244n/a self.radioBg=Radiobutton(
245n/a frameFgBg, variable=self.fgHilite, value=0,
246n/a text='Background', command=self.SetColourSampleBinding)
247n/a self.fgHilite.set(1)
248n/a buttonSaveCustomTheme = Button(
249n/a frameCustom, text='Save as New Custom Theme',
250n/a command=self.SaveAsNewTheme)
251n/a #frameTheme
252n/a labelTypeTitle = Label(frameTheme, text='Select : ')
253n/a self.radioThemeBuiltin = Radiobutton(
254n/a frameTheme, variable=self.themeIsBuiltin, value=1,
255n/a command=self.SetThemeType, text='a Built-in Theme')
256n/a self.radioThemeCustom = Radiobutton(
257n/a frameTheme, variable=self.themeIsBuiltin, value=0,
258n/a command=self.SetThemeType, text='a Custom Theme')
259n/a self.optMenuThemeBuiltin = DynOptionMenu(
260n/a frameTheme, self.builtinTheme, None, command=None)
261n/a self.optMenuThemeCustom=DynOptionMenu(
262n/a frameTheme, self.customTheme, None, command=None)
263n/a self.buttonDeleteCustomTheme=Button(
264n/a frameTheme, text='Delete Custom Theme',
265n/a command=self.DeleteCustomTheme)
266n/a self.new_custom_theme = Label(frameTheme, bd=2)
267n/a
268n/a ##widget packing
269n/a #body
270n/a frameCustom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
271n/a frameTheme.pack(side=LEFT, padx=5, pady=5, fill=Y)
272n/a #frameCustom
273n/a self.frameColourSet.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
274n/a frameFgBg.pack(side=TOP, padx=5, pady=0)
275n/a self.textHighlightSample.pack(
276n/a side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
277n/a buttonSetColour.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
278n/a self.optMenuHighlightTarget.pack(
279n/a side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
280n/a self.radioFg.pack(side=LEFT, anchor=E)
281n/a self.radioBg.pack(side=RIGHT, anchor=W)
282n/a buttonSaveCustomTheme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
283n/a #frameTheme
284n/a labelTypeTitle.pack(side=TOP, anchor=W, padx=5, pady=5)
285n/a self.radioThemeBuiltin.pack(side=TOP, anchor=W, padx=5)
286n/a self.radioThemeCustom.pack(side=TOP, anchor=W, padx=5, pady=2)
287n/a self.optMenuThemeBuiltin.pack(side=TOP, fill=X, padx=5, pady=5)
288n/a self.optMenuThemeCustom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
289n/a self.buttonDeleteCustomTheme.pack(side=TOP, fill=X, padx=5, pady=5)
290n/a self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
291n/a return frame
292n/a
293n/a def CreatePageKeys(self):
294n/a parent = self.parent
295n/a self.bindingTarget = StringVar(parent)
296n/a self.builtinKeys = StringVar(parent)
297n/a self.customKeys = StringVar(parent)
298n/a self.keysAreBuiltin = BooleanVar(parent)
299n/a self.keyBinding = StringVar(parent)
300n/a
301n/a ##widget creation
302n/a #body frame
303n/a frame = self.tabPages.pages['Keys'].frame
304n/a #body section frames
305n/a frameCustom = LabelFrame(
306n/a frame, borderwidth=2, relief=GROOVE,
307n/a text=' Custom Key Bindings ')
308n/a frameKeySets = LabelFrame(
309n/a frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
310n/a #frameCustom
311n/a frameTarget = Frame(frameCustom)
312n/a labelTargetTitle = Label(frameTarget, text='Action - Key(s)')
313n/a scrollTargetY = Scrollbar(frameTarget)
314n/a scrollTargetX = Scrollbar(frameTarget, orient=HORIZONTAL)
315n/a self.listBindings = Listbox(
316n/a frameTarget, takefocus=FALSE, exportselection=FALSE)
317n/a self.listBindings.bind('<ButtonRelease-1>', self.KeyBindingSelected)
318n/a scrollTargetY.config(command=self.listBindings.yview)
319n/a scrollTargetX.config(command=self.listBindings.xview)
320n/a self.listBindings.config(yscrollcommand=scrollTargetY.set)
321n/a self.listBindings.config(xscrollcommand=scrollTargetX.set)
322n/a self.buttonNewKeys = Button(
323n/a frameCustom, text='Get New Keys for Selection',
324n/a command=self.GetNewKeys, state=DISABLED)
325n/a #frameKeySets
326n/a frames = [Frame(frameKeySets, padx=2, pady=2, borderwidth=0)
327n/a for i in range(2)]
328n/a self.radioKeysBuiltin = Radiobutton(
329n/a frames[0], variable=self.keysAreBuiltin, value=1,
330n/a command=self.SetKeysType, text='Use a Built-in Key Set')
331n/a self.radioKeysCustom = Radiobutton(
332n/a frames[0], variable=self.keysAreBuiltin, value=0,
333n/a command=self.SetKeysType, text='Use a Custom Key Set')
334n/a self.optMenuKeysBuiltin = DynOptionMenu(
335n/a frames[0], self.builtinKeys, None, command=None)
336n/a self.optMenuKeysCustom = DynOptionMenu(
337n/a frames[0], self.customKeys, None, command=None)
338n/a self.buttonDeleteCustomKeys = Button(
339n/a frames[1], text='Delete Custom Key Set',
340n/a command=self.DeleteCustomKeys)
341n/a buttonSaveCustomKeys = Button(
342n/a frames[1], text='Save as New Custom Key Set',
343n/a command=self.SaveAsNewKeySet)
344n/a self.new_custom_keys = Label(frames[0], bd=2)
345n/a
346n/a ##widget packing
347n/a #body
348n/a frameCustom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
349n/a frameKeySets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
350n/a #frameCustom
351n/a self.buttonNewKeys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
352n/a frameTarget.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
353n/a #frame target
354n/a frameTarget.columnconfigure(0, weight=1)
355n/a frameTarget.rowconfigure(1, weight=1)
356n/a labelTargetTitle.grid(row=0, column=0, columnspan=2, sticky=W)
357n/a self.listBindings.grid(row=1, column=0, sticky=NSEW)
358n/a scrollTargetY.grid(row=1, column=1, sticky=NS)
359n/a scrollTargetX.grid(row=2, column=0, sticky=EW)
360n/a #frameKeySets
361n/a self.radioKeysBuiltin.grid(row=0, column=0, sticky=W+NS)
362n/a self.radioKeysCustom.grid(row=1, column=0, sticky=W+NS)
363n/a self.optMenuKeysBuiltin.grid(row=0, column=1, sticky=NSEW)
364n/a self.optMenuKeysCustom.grid(row=1, column=1, sticky=NSEW)
365n/a self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
366n/a self.buttonDeleteCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2)
367n/a buttonSaveCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2)
368n/a frames[0].pack(side=TOP, fill=BOTH, expand=True)
369n/a frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
370n/a return frame
371n/a
372n/a def CreatePageGeneral(self):
373n/a parent = self.parent
374n/a self.winWidth = StringVar(parent)
375n/a self.winHeight = StringVar(parent)
376n/a self.startupEdit = IntVar(parent)
377n/a self.autoSave = IntVar(parent)
378n/a self.encoding = StringVar(parent)
379n/a self.userHelpBrowser = BooleanVar(parent)
380n/a self.helpBrowser = StringVar(parent)
381n/a
382n/a #widget creation
383n/a #body
384n/a frame = self.tabPages.pages['General'].frame
385n/a #body section frames
386n/a frameRun = LabelFrame(frame, borderwidth=2, relief=GROOVE,
387n/a text=' Startup Preferences ')
388n/a frameSave = LabelFrame(frame, borderwidth=2, relief=GROOVE,
389n/a text=' Autosave Preferences ')
390n/a frameWinSize = Frame(frame, borderwidth=2, relief=GROOVE)
391n/a frameHelp = LabelFrame(frame, borderwidth=2, relief=GROOVE,
392n/a text=' Additional Help Sources ')
393n/a #frameRun
394n/a labelRunChoiceTitle = Label(frameRun, text='At Startup')
395n/a self.radioStartupEdit = Radiobutton(
396n/a frameRun, variable=self.startupEdit, value=1,
397n/a text="Open Edit Window")
398n/a self.radioStartupShell = Radiobutton(
399n/a frameRun, variable=self.startupEdit, value=0,
400n/a text='Open Shell Window')
401n/a #frameSave
402n/a labelRunSaveTitle = Label(frameSave, text='At Start of Run (F5) ')
403n/a self.radioSaveAsk = Radiobutton(
404n/a frameSave, variable=self.autoSave, value=0,
405n/a text="Prompt to Save")
406n/a self.radioSaveAuto = Radiobutton(
407n/a frameSave, variable=self.autoSave, value=1,
408n/a text='No Prompt')
409n/a #frameWinSize
410n/a labelWinSizeTitle = Label(
411n/a frameWinSize, text='Initial Window Size (in characters)')
412n/a labelWinWidthTitle = Label(frameWinSize, text='Width')
413n/a self.entryWinWidth = Entry(
414n/a frameWinSize, textvariable=self.winWidth, width=3)
415n/a labelWinHeightTitle = Label(frameWinSize, text='Height')
416n/a self.entryWinHeight = Entry(
417n/a frameWinSize, textvariable=self.winHeight, width=3)
418n/a #frameHelp
419n/a frameHelpList = Frame(frameHelp)
420n/a frameHelpListButtons = Frame(frameHelpList)
421n/a scrollHelpList = Scrollbar(frameHelpList)
422n/a self.listHelp = Listbox(
423n/a frameHelpList, height=5, takefocus=FALSE,
424n/a exportselection=FALSE)
425n/a scrollHelpList.config(command=self.listHelp.yview)
426n/a self.listHelp.config(yscrollcommand=scrollHelpList.set)
427n/a self.listHelp.bind('<ButtonRelease-1>', self.HelpSourceSelected)
428n/a self.buttonHelpListEdit = Button(
429n/a frameHelpListButtons, text='Edit', state=DISABLED,
430n/a width=8, command=self.HelpListItemEdit)
431n/a self.buttonHelpListAdd = Button(
432n/a frameHelpListButtons, text='Add',
433n/a width=8, command=self.HelpListItemAdd)
434n/a self.buttonHelpListRemove = Button(
435n/a frameHelpListButtons, text='Remove', state=DISABLED,
436n/a width=8, command=self.HelpListItemRemove)
437n/a
438n/a #widget packing
439n/a #body
440n/a frameRun.pack(side=TOP, padx=5, pady=5, fill=X)
441n/a frameSave.pack(side=TOP, padx=5, pady=5, fill=X)
442n/a frameWinSize.pack(side=TOP, padx=5, pady=5, fill=X)
443n/a frameHelp.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
444n/a #frameRun
445n/a labelRunChoiceTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
446n/a self.radioStartupShell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
447n/a self.radioStartupEdit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
448n/a #frameSave
449n/a labelRunSaveTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
450n/a self.radioSaveAuto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
451n/a self.radioSaveAsk.pack(side=RIGHT, anchor=W, padx=5, pady=5)
452n/a #frameWinSize
453n/a labelWinSizeTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
454n/a self.entryWinHeight.pack(side=RIGHT, anchor=E, padx=10, pady=5)
455n/a labelWinHeightTitle.pack(side=RIGHT, anchor=E, pady=5)
456n/a self.entryWinWidth.pack(side=RIGHT, anchor=E, padx=10, pady=5)
457n/a labelWinWidthTitle.pack(side=RIGHT, anchor=E, pady=5)
458n/a #frameHelp
459n/a frameHelpListButtons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
460n/a frameHelpList.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
461n/a scrollHelpList.pack(side=RIGHT, anchor=W, fill=Y)
462n/a self.listHelp.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
463n/a self.buttonHelpListEdit.pack(side=TOP, anchor=W, pady=5)
464n/a self.buttonHelpListAdd.pack(side=TOP, anchor=W)
465n/a self.buttonHelpListRemove.pack(side=TOP, anchor=W, pady=5)
466n/a return frame
467n/a
468n/a def AttachVarCallbacks(self):
469n/a self.fontSize.trace_add('write', self.VarChanged_font)
470n/a self.fontName.trace_add('write', self.VarChanged_font)
471n/a self.fontBold.trace_add('write', self.VarChanged_font)
472n/a self.spaceNum.trace_add('write', self.VarChanged_spaceNum)
473n/a self.colour.trace_add('write', self.VarChanged_colour)
474n/a self.builtinTheme.trace_add('write', self.VarChanged_builtinTheme)
475n/a self.customTheme.trace_add('write', self.VarChanged_customTheme)
476n/a self.themeIsBuiltin.trace_add('write', self.VarChanged_themeIsBuiltin)
477n/a self.highlightTarget.trace_add('write', self.VarChanged_highlightTarget)
478n/a self.keyBinding.trace_add('write', self.VarChanged_keyBinding)
479n/a self.builtinKeys.trace_add('write', self.VarChanged_builtinKeys)
480n/a self.customKeys.trace_add('write', self.VarChanged_customKeys)
481n/a self.keysAreBuiltin.trace_add('write', self.VarChanged_keysAreBuiltin)
482n/a self.winWidth.trace_add('write', self.VarChanged_winWidth)
483n/a self.winHeight.trace_add('write', self.VarChanged_winHeight)
484n/a self.startupEdit.trace_add('write', self.VarChanged_startupEdit)
485n/a self.autoSave.trace_add('write', self.VarChanged_autoSave)
486n/a self.encoding.trace_add('write', self.VarChanged_encoding)
487n/a
488n/a def remove_var_callbacks(self):
489n/a "Remove callbacks to prevent memory leaks."
490n/a for var in (
491n/a self.fontSize, self.fontName, self.fontBold,
492n/a self.spaceNum, self.colour, self.builtinTheme,
493n/a self.customTheme, self.themeIsBuiltin, self.highlightTarget,
494n/a self.keyBinding, self.builtinKeys, self.customKeys,
495n/a self.keysAreBuiltin, self.winWidth, self.winHeight,
496n/a self.startupEdit, self.autoSave, self.encoding,):
497n/a var.trace_remove('write', var.trace_info()[0][1])
498n/a
499n/a def VarChanged_font(self, *params):
500n/a '''When one font attribute changes, save them all, as they are
501n/a not independent from each other. In particular, when we are
502n/a overriding the default font, we need to write out everything.
503n/a '''
504n/a value = self.fontName.get()
505n/a self.AddChangedItem('main', 'EditorWindow', 'font', value)
506n/a value = self.fontSize.get()
507n/a self.AddChangedItem('main', 'EditorWindow', 'font-size', value)
508n/a value = self.fontBold.get()
509n/a self.AddChangedItem('main', 'EditorWindow', 'font-bold', value)
510n/a
511n/a def VarChanged_spaceNum(self, *params):
512n/a value = self.spaceNum.get()
513n/a self.AddChangedItem('main', 'Indent', 'num-spaces', value)
514n/a
515n/a def VarChanged_colour(self, *params):
516n/a self.OnNewColourSet()
517n/a
518n/a def VarChanged_builtinTheme(self, *params):
519n/a oldthemes = ('IDLE Classic', 'IDLE New')
520n/a value = self.builtinTheme.get()
521n/a if value not in oldthemes:
522n/a if idleConf.GetOption('main', 'Theme', 'name') not in oldthemes:
523n/a self.AddChangedItem('main', 'Theme', 'name', oldthemes[0])
524n/a self.AddChangedItem('main', 'Theme', 'name2', value)
525n/a self.new_custom_theme.config(text='New theme, see Help',
526n/a fg='#500000')
527n/a else:
528n/a self.AddChangedItem('main', 'Theme', 'name', value)
529n/a self.AddChangedItem('main', 'Theme', 'name2', '')
530n/a self.new_custom_theme.config(text='', fg='black')
531n/a self.PaintThemeSample()
532n/a
533n/a def VarChanged_customTheme(self, *params):
534n/a value = self.customTheme.get()
535n/a if value != '- no custom themes -':
536n/a self.AddChangedItem('main', 'Theme', 'name', value)
537n/a self.PaintThemeSample()
538n/a
539n/a def VarChanged_themeIsBuiltin(self, *params):
540n/a value = self.themeIsBuiltin.get()
541n/a self.AddChangedItem('main', 'Theme', 'default', value)
542n/a if value:
543n/a self.VarChanged_builtinTheme()
544n/a else:
545n/a self.VarChanged_customTheme()
546n/a
547n/a def VarChanged_highlightTarget(self, *params):
548n/a self.SetHighlightTarget()
549n/a
550n/a def VarChanged_keyBinding(self, *params):
551n/a value = self.keyBinding.get()
552n/a keySet = self.customKeys.get()
553n/a event = self.listBindings.get(ANCHOR).split()[0]
554n/a if idleConf.IsCoreBinding(event):
555n/a #this is a core keybinding
556n/a self.AddChangedItem('keys', keySet, event, value)
557n/a else: #this is an extension key binding
558n/a extName = idleConf.GetExtnNameForEvent(event)
559n/a extKeybindSection = extName + '_cfgBindings'
560n/a self.AddChangedItem('extensions', extKeybindSection, event, value)
561n/a
562n/a def VarChanged_builtinKeys(self, *params):
563n/a oldkeys = (
564n/a 'IDLE Classic Windows',
565n/a 'IDLE Classic Unix',
566n/a 'IDLE Classic Mac',
567n/a 'IDLE Classic OSX',
568n/a )
569n/a value = self.builtinKeys.get()
570n/a if value not in oldkeys:
571n/a if idleConf.GetOption('main', 'Keys', 'name') not in oldkeys:
572n/a self.AddChangedItem('main', 'Keys', 'name', oldkeys[0])
573n/a self.AddChangedItem('main', 'Keys', 'name2', value)
574n/a self.new_custom_keys.config(text='New key set, see Help',
575n/a fg='#500000')
576n/a else:
577n/a self.AddChangedItem('main', 'Keys', 'name', value)
578n/a self.AddChangedItem('main', 'Keys', 'name2', '')
579n/a self.new_custom_keys.config(text='', fg='black')
580n/a self.LoadKeysList(value)
581n/a
582n/a def VarChanged_customKeys(self, *params):
583n/a value = self.customKeys.get()
584n/a if value != '- no custom keys -':
585n/a self.AddChangedItem('main', 'Keys', 'name', value)
586n/a self.LoadKeysList(value)
587n/a
588n/a def VarChanged_keysAreBuiltin(self, *params):
589n/a value = self.keysAreBuiltin.get()
590n/a self.AddChangedItem('main', 'Keys', 'default', value)
591n/a if value:
592n/a self.VarChanged_builtinKeys()
593n/a else:
594n/a self.VarChanged_customKeys()
595n/a
596n/a def VarChanged_winWidth(self, *params):
597n/a value = self.winWidth.get()
598n/a self.AddChangedItem('main', 'EditorWindow', 'width', value)
599n/a
600n/a def VarChanged_winHeight(self, *params):
601n/a value = self.winHeight.get()
602n/a self.AddChangedItem('main', 'EditorWindow', 'height', value)
603n/a
604n/a def VarChanged_startupEdit(self, *params):
605n/a value = self.startupEdit.get()
606n/a self.AddChangedItem('main', 'General', 'editor-on-startup', value)
607n/a
608n/a def VarChanged_autoSave(self, *params):
609n/a value = self.autoSave.get()
610n/a self.AddChangedItem('main', 'General', 'autosave', value)
611n/a
612n/a def VarChanged_encoding(self, *params):
613n/a value = self.encoding.get()
614n/a self.AddChangedItem('main', 'EditorWindow', 'encoding', value)
615n/a
616n/a def ResetChangedItems(self):
617n/a #When any config item is changed in this dialog, an entry
618n/a #should be made in the relevant section (config type) of this
619n/a #dictionary. The key should be the config file section name and the
620n/a #value a dictionary, whose key:value pairs are item=value pairs for
621n/a #that config file section.
622n/a self.changedItems = {'main':{}, 'highlight':{}, 'keys':{},
623n/a 'extensions':{}}
624n/a
625n/a def AddChangedItem(self, typ, section, item, value):
626n/a value = str(value) #make sure we use a string
627n/a if section not in self.changedItems[typ]:
628n/a self.changedItems[typ][section] = {}
629n/a self.changedItems[typ][section][item] = value
630n/a
631n/a def GetDefaultItems(self):
632n/a dItems={'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}}
633n/a for configType in dItems:
634n/a sections = idleConf.GetSectionList('default', configType)
635n/a for section in sections:
636n/a dItems[configType][section] = {}
637n/a options = idleConf.defaultCfg[configType].GetOptionList(section)
638n/a for option in options:
639n/a dItems[configType][section][option] = (
640n/a idleConf.defaultCfg[configType].Get(section, option))
641n/a return dItems
642n/a
643n/a def SetThemeType(self):
644n/a if self.themeIsBuiltin.get():
645n/a self.optMenuThemeBuiltin.config(state=NORMAL)
646n/a self.optMenuThemeCustom.config(state=DISABLED)
647n/a self.buttonDeleteCustomTheme.config(state=DISABLED)
648n/a else:
649n/a self.optMenuThemeBuiltin.config(state=DISABLED)
650n/a self.radioThemeCustom.config(state=NORMAL)
651n/a self.optMenuThemeCustom.config(state=NORMAL)
652n/a self.buttonDeleteCustomTheme.config(state=NORMAL)
653n/a
654n/a def SetKeysType(self):
655n/a if self.keysAreBuiltin.get():
656n/a self.optMenuKeysBuiltin.config(state=NORMAL)
657n/a self.optMenuKeysCustom.config(state=DISABLED)
658n/a self.buttonDeleteCustomKeys.config(state=DISABLED)
659n/a else:
660n/a self.optMenuKeysBuiltin.config(state=DISABLED)
661n/a self.radioKeysCustom.config(state=NORMAL)
662n/a self.optMenuKeysCustom.config(state=NORMAL)
663n/a self.buttonDeleteCustomKeys.config(state=NORMAL)
664n/a
665n/a def GetNewKeys(self):
666n/a listIndex = self.listBindings.index(ANCHOR)
667n/a binding = self.listBindings.get(listIndex)
668n/a bindName = binding.split()[0] #first part, up to first space
669n/a if self.keysAreBuiltin.get():
670n/a currentKeySetName = self.builtinKeys.get()
671n/a else:
672n/a currentKeySetName = self.customKeys.get()
673n/a currentBindings = idleConf.GetCurrentKeySet()
674n/a if currentKeySetName in self.changedItems['keys']: #unsaved changes
675n/a keySetChanges = self.changedItems['keys'][currentKeySetName]
676n/a for event in keySetChanges:
677n/a currentBindings[event] = keySetChanges[event].split()
678n/a currentKeySequences = list(currentBindings.values())
679n/a newKeys = GetKeysDialog(self, 'Get New Keys', bindName,
680n/a currentKeySequences).result
681n/a if newKeys: #new keys were specified
682n/a if self.keysAreBuiltin.get(): #current key set is a built-in
683n/a message = ('Your changes will be saved as a new Custom Key Set.'
684n/a ' Enter a name for your new Custom Key Set below.')
685n/a newKeySet = self.GetNewKeysName(message)
686n/a if not newKeySet: #user cancelled custom key set creation
687n/a self.listBindings.select_set(listIndex)
688n/a self.listBindings.select_anchor(listIndex)
689n/a return
690n/a else: #create new custom key set based on previously active key set
691n/a self.CreateNewKeySet(newKeySet)
692n/a self.listBindings.delete(listIndex)
693n/a self.listBindings.insert(listIndex, bindName+' - '+newKeys)
694n/a self.listBindings.select_set(listIndex)
695n/a self.listBindings.select_anchor(listIndex)
696n/a self.keyBinding.set(newKeys)
697n/a else:
698n/a self.listBindings.select_set(listIndex)
699n/a self.listBindings.select_anchor(listIndex)
700n/a
701n/a def GetNewKeysName(self, message):
702n/a usedNames = (idleConf.GetSectionList('user', 'keys') +
703n/a idleConf.GetSectionList('default', 'keys'))
704n/a newKeySet = SectionName(
705n/a self, 'New Custom Key Set', message, usedNames).result
706n/a return newKeySet
707n/a
708n/a def SaveAsNewKeySet(self):
709n/a newKeysName = self.GetNewKeysName('New Key Set Name:')
710n/a if newKeysName:
711n/a self.CreateNewKeySet(newKeysName)
712n/a
713n/a def KeyBindingSelected(self, event):
714n/a self.buttonNewKeys.config(state=NORMAL)
715n/a
716n/a def CreateNewKeySet(self, newKeySetName):
717n/a #creates new custom key set based on the previously active key set,
718n/a #and makes the new key set active
719n/a if self.keysAreBuiltin.get():
720n/a prevKeySetName = self.builtinKeys.get()
721n/a else:
722n/a prevKeySetName = self.customKeys.get()
723n/a prevKeys = idleConf.GetCoreKeys(prevKeySetName)
724n/a newKeys = {}
725n/a for event in prevKeys: #add key set to changed items
726n/a eventName = event[2:-2] #trim off the angle brackets
727n/a binding = ' '.join(prevKeys[event])
728n/a newKeys[eventName] = binding
729n/a #handle any unsaved changes to prev key set
730n/a if prevKeySetName in self.changedItems['keys']:
731n/a keySetChanges = self.changedItems['keys'][prevKeySetName]
732n/a for event in keySetChanges:
733n/a newKeys[event] = keySetChanges[event]
734n/a #save the new theme
735n/a self.SaveNewKeySet(newKeySetName, newKeys)
736n/a #change gui over to the new key set
737n/a customKeyList = idleConf.GetSectionList('user', 'keys')
738n/a customKeyList.sort()
739n/a self.optMenuKeysCustom.SetMenu(customKeyList, newKeySetName)
740n/a self.keysAreBuiltin.set(0)
741n/a self.SetKeysType()
742n/a
743n/a def LoadKeysList(self, keySetName):
744n/a reselect = 0
745n/a newKeySet = 0
746n/a if self.listBindings.curselection():
747n/a reselect = 1
748n/a listIndex = self.listBindings.index(ANCHOR)
749n/a keySet = idleConf.GetKeySet(keySetName)
750n/a bindNames = list(keySet.keys())
751n/a bindNames.sort()
752n/a self.listBindings.delete(0, END)
753n/a for bindName in bindNames:
754n/a key = ' '.join(keySet[bindName]) #make key(s) into a string
755n/a bindName = bindName[2:-2] #trim off the angle brackets
756n/a if keySetName in self.changedItems['keys']:
757n/a #handle any unsaved changes to this key set
758n/a if bindName in self.changedItems['keys'][keySetName]:
759n/a key = self.changedItems['keys'][keySetName][bindName]
760n/a self.listBindings.insert(END, bindName+' - '+key)
761n/a if reselect:
762n/a self.listBindings.see(listIndex)
763n/a self.listBindings.select_set(listIndex)
764n/a self.listBindings.select_anchor(listIndex)
765n/a
766n/a def DeleteCustomKeys(self):
767n/a keySetName=self.customKeys.get()
768n/a delmsg = 'Are you sure you wish to delete the key set %r ?'
769n/a if not tkMessageBox.askyesno(
770n/a 'Delete Key Set', delmsg % keySetName, parent=self):
771n/a return
772n/a self.DeactivateCurrentConfig()
773n/a #remove key set from config
774n/a idleConf.userCfg['keys'].remove_section(keySetName)
775n/a if keySetName in self.changedItems['keys']:
776n/a del(self.changedItems['keys'][keySetName])
777n/a #write changes
778n/a idleConf.userCfg['keys'].Save()
779n/a #reload user key set list
780n/a itemList = idleConf.GetSectionList('user', 'keys')
781n/a itemList.sort()
782n/a if not itemList:
783n/a self.radioKeysCustom.config(state=DISABLED)
784n/a self.optMenuKeysCustom.SetMenu(itemList, '- no custom keys -')
785n/a else:
786n/a self.optMenuKeysCustom.SetMenu(itemList, itemList[0])
787n/a #revert to default key set
788n/a self.keysAreBuiltin.set(idleConf.defaultCfg['main']
789n/a .Get('Keys', 'default'))
790n/a self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
791n/a or idleConf.default_keys())
792n/a #user can't back out of these changes, they must be applied now
793n/a self.SaveAllChangedConfigs()
794n/a self.ActivateConfigChanges()
795n/a self.SetKeysType()
796n/a
797n/a def DeleteCustomTheme(self):
798n/a themeName = self.customTheme.get()
799n/a delmsg = 'Are you sure you wish to delete the theme %r ?'
800n/a if not tkMessageBox.askyesno(
801n/a 'Delete Theme', delmsg % themeName, parent=self):
802n/a return
803n/a self.DeactivateCurrentConfig()
804n/a #remove theme from config
805n/a idleConf.userCfg['highlight'].remove_section(themeName)
806n/a if themeName in self.changedItems['highlight']:
807n/a del(self.changedItems['highlight'][themeName])
808n/a #write changes
809n/a idleConf.userCfg['highlight'].Save()
810n/a #reload user theme list
811n/a itemList = idleConf.GetSectionList('user', 'highlight')
812n/a itemList.sort()
813n/a if not itemList:
814n/a self.radioThemeCustom.config(state=DISABLED)
815n/a self.optMenuThemeCustom.SetMenu(itemList, '- no custom themes -')
816n/a else:
817n/a self.optMenuThemeCustom.SetMenu(itemList, itemList[0])
818n/a #revert to default theme
819n/a self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
820n/a self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
821n/a #user can't back out of these changes, they must be applied now
822n/a self.SaveAllChangedConfigs()
823n/a self.ActivateConfigChanges()
824n/a self.SetThemeType()
825n/a
826n/a def GetColour(self):
827n/a target = self.highlightTarget.get()
828n/a prevColour = self.frameColourSet.cget('bg')
829n/a rgbTuplet, colourString = tkColorChooser.askcolor(
830n/a parent=self, title='Pick new colour for : '+target,
831n/a initialcolor=prevColour)
832n/a if colourString and (colourString != prevColour):
833n/a #user didn't cancel, and they chose a new colour
834n/a if self.themeIsBuiltin.get(): #current theme is a built-in
835n/a message = ('Your changes will be saved as a new Custom Theme. '
836n/a 'Enter a name for your new Custom Theme below.')
837n/a newTheme = self.GetNewThemeName(message)
838n/a if not newTheme: #user cancelled custom theme creation
839n/a return
840n/a else: #create new custom theme based on previously active theme
841n/a self.CreateNewTheme(newTheme)
842n/a self.colour.set(colourString)
843n/a else: #current theme is user defined
844n/a self.colour.set(colourString)
845n/a
846n/a def OnNewColourSet(self):
847n/a newColour=self.colour.get()
848n/a self.frameColourSet.config(bg=newColour) #set sample
849n/a plane ='foreground' if self.fgHilite.get() else 'background'
850n/a sampleElement = self.themeElements[self.highlightTarget.get()][0]
851n/a self.textHighlightSample.tag_config(sampleElement, **{plane:newColour})
852n/a theme = self.customTheme.get()
853n/a themeElement = sampleElement + '-' + plane
854n/a self.AddChangedItem('highlight', theme, themeElement, newColour)
855n/a
856n/a def GetNewThemeName(self, message):
857n/a usedNames = (idleConf.GetSectionList('user', 'highlight') +
858n/a idleConf.GetSectionList('default', 'highlight'))
859n/a newTheme = SectionName(
860n/a self, 'New Custom Theme', message, usedNames).result
861n/a return newTheme
862n/a
863n/a def SaveAsNewTheme(self):
864n/a newThemeName = self.GetNewThemeName('New Theme Name:')
865n/a if newThemeName:
866n/a self.CreateNewTheme(newThemeName)
867n/a
868n/a def CreateNewTheme(self, newThemeName):
869n/a #creates new custom theme based on the previously active theme,
870n/a #and makes the new theme active
871n/a if self.themeIsBuiltin.get():
872n/a themeType = 'default'
873n/a themeName = self.builtinTheme.get()
874n/a else:
875n/a themeType = 'user'
876n/a themeName = self.customTheme.get()
877n/a newTheme = idleConf.GetThemeDict(themeType, themeName)
878n/a #apply any of the old theme's unsaved changes to the new theme
879n/a if themeName in self.changedItems['highlight']:
880n/a themeChanges = self.changedItems['highlight'][themeName]
881n/a for element in themeChanges:
882n/a newTheme[element] = themeChanges[element]
883n/a #save the new theme
884n/a self.SaveNewTheme(newThemeName, newTheme)
885n/a #change gui over to the new theme
886n/a customThemeList = idleConf.GetSectionList('user', 'highlight')
887n/a customThemeList.sort()
888n/a self.optMenuThemeCustom.SetMenu(customThemeList, newThemeName)
889n/a self.themeIsBuiltin.set(0)
890n/a self.SetThemeType()
891n/a
892n/a def OnListFontButtonRelease(self, event):
893n/a font = self.listFontName.get(ANCHOR)
894n/a self.fontName.set(font.lower())
895n/a self.SetFontSample()
896n/a
897n/a def SetFontSample(self, event=None):
898n/a fontName = self.fontName.get()
899n/a fontWeight = tkFont.BOLD if self.fontBold.get() else tkFont.NORMAL
900n/a newFont = (fontName, self.fontSize.get(), fontWeight)
901n/a self.labelFontSample.config(font=newFont)
902n/a self.textHighlightSample.configure(font=newFont)
903n/a
904n/a def SetHighlightTarget(self):
905n/a if self.highlightTarget.get() == 'Cursor': #bg not possible
906n/a self.radioFg.config(state=DISABLED)
907n/a self.radioBg.config(state=DISABLED)
908n/a self.fgHilite.set(1)
909n/a else: #both fg and bg can be set
910n/a self.radioFg.config(state=NORMAL)
911n/a self.radioBg.config(state=NORMAL)
912n/a self.fgHilite.set(1)
913n/a self.SetColourSample()
914n/a
915n/a def SetColourSampleBinding(self, *args):
916n/a self.SetColourSample()
917n/a
918n/a def SetColourSample(self):
919n/a #set the colour smaple area
920n/a tag = self.themeElements[self.highlightTarget.get()][0]
921n/a plane = 'foreground' if self.fgHilite.get() else 'background'
922n/a colour = self.textHighlightSample.tag_cget(tag, plane)
923n/a self.frameColourSet.config(bg=colour)
924n/a
925n/a def PaintThemeSample(self):
926n/a if self.themeIsBuiltin.get(): #a default theme
927n/a theme = self.builtinTheme.get()
928n/a else: #a user theme
929n/a theme = self.customTheme.get()
930n/a for elementTitle in self.themeElements:
931n/a element = self.themeElements[elementTitle][0]
932n/a colours = idleConf.GetHighlight(theme, element)
933n/a if element == 'cursor': #cursor sample needs special painting
934n/a colours['background'] = idleConf.GetHighlight(
935n/a theme, 'normal', fgBg='bg')
936n/a #handle any unsaved changes to this theme
937n/a if theme in self.changedItems['highlight']:
938n/a themeDict = self.changedItems['highlight'][theme]
939n/a if element + '-foreground' in themeDict:
940n/a colours['foreground'] = themeDict[element + '-foreground']
941n/a if element + '-background' in themeDict:
942n/a colours['background'] = themeDict[element + '-background']
943n/a self.textHighlightSample.tag_config(element, **colours)
944n/a self.SetColourSample()
945n/a
946n/a def HelpSourceSelected(self, event):
947n/a self.SetHelpListButtonStates()
948n/a
949n/a def SetHelpListButtonStates(self):
950n/a if self.listHelp.size() < 1: #no entries in list
951n/a self.buttonHelpListEdit.config(state=DISABLED)
952n/a self.buttonHelpListRemove.config(state=DISABLED)
953n/a else: #there are some entries
954n/a if self.listHelp.curselection(): #there currently is a selection
955n/a self.buttonHelpListEdit.config(state=NORMAL)
956n/a self.buttonHelpListRemove.config(state=NORMAL)
957n/a else: #there currently is not a selection
958n/a self.buttonHelpListEdit.config(state=DISABLED)
959n/a self.buttonHelpListRemove.config(state=DISABLED)
960n/a
961n/a def HelpListItemAdd(self):
962n/a helpSource = HelpSource(self, 'New Help Source',
963n/a ).result
964n/a if helpSource:
965n/a self.userHelpList.append((helpSource[0], helpSource[1]))
966n/a self.listHelp.insert(END, helpSource[0])
967n/a self.UpdateUserHelpChangedItems()
968n/a self.SetHelpListButtonStates()
969n/a
970n/a def HelpListItemEdit(self):
971n/a itemIndex = self.listHelp.index(ANCHOR)
972n/a helpSource = self.userHelpList[itemIndex]
973n/a newHelpSource = HelpSource(
974n/a self, 'Edit Help Source',
975n/a menuitem=helpSource[0],
976n/a filepath=helpSource[1],
977n/a ).result
978n/a if newHelpSource and newHelpSource != helpSource:
979n/a self.userHelpList[itemIndex] = newHelpSource
980n/a self.listHelp.delete(itemIndex)
981n/a self.listHelp.insert(itemIndex, newHelpSource[0])
982n/a self.UpdateUserHelpChangedItems()
983n/a self.SetHelpListButtonStates()
984n/a
985n/a def HelpListItemRemove(self):
986n/a itemIndex = self.listHelp.index(ANCHOR)
987n/a del(self.userHelpList[itemIndex])
988n/a self.listHelp.delete(itemIndex)
989n/a self.UpdateUserHelpChangedItems()
990n/a self.SetHelpListButtonStates()
991n/a
992n/a def UpdateUserHelpChangedItems(self):
993n/a "Clear and rebuild the HelpFiles section in self.changedItems"
994n/a self.changedItems['main']['HelpFiles'] = {}
995n/a for num in range(1, len(self.userHelpList) + 1):
996n/a self.AddChangedItem(
997n/a 'main', 'HelpFiles', str(num),
998n/a ';'.join(self.userHelpList[num-1][:2]))
999n/a
1000n/a def LoadFontCfg(self):
1001n/a ##base editor font selection list
1002n/a fonts = list(tkFont.families(self))
1003n/a fonts.sort()
1004n/a for font in fonts:
1005n/a self.listFontName.insert(END, font)
1006n/a configuredFont = idleConf.GetFont(self, 'main', 'EditorWindow')
1007n/a fontName = configuredFont[0].lower()
1008n/a fontSize = configuredFont[1]
1009n/a fontBold = configuredFont[2]=='bold'
1010n/a self.fontName.set(fontName)
1011n/a lc_fonts = [s.lower() for s in fonts]
1012n/a try:
1013n/a currentFontIndex = lc_fonts.index(fontName)
1014n/a self.listFontName.see(currentFontIndex)
1015n/a self.listFontName.select_set(currentFontIndex)
1016n/a self.listFontName.select_anchor(currentFontIndex)
1017n/a except ValueError:
1018n/a pass
1019n/a ##font size dropdown
1020n/a self.optMenuFontSize.SetMenu(('7', '8', '9', '10', '11', '12', '13',
1021n/a '14', '16', '18', '20', '22',
1022n/a '25', '29', '34', '40'), fontSize )
1023n/a ##fontWeight
1024n/a self.fontBold.set(fontBold)
1025n/a ##font sample
1026n/a self.SetFontSample()
1027n/a
1028n/a def LoadTabCfg(self):
1029n/a ##indent sizes
1030n/a spaceNum = idleConf.GetOption(
1031n/a 'main', 'Indent', 'num-spaces', default=4, type='int')
1032n/a self.spaceNum.set(spaceNum)
1033n/a
1034n/a def LoadThemeCfg(self):
1035n/a ##current theme type radiobutton
1036n/a self.themeIsBuiltin.set(idleConf.GetOption(
1037n/a 'main', 'Theme', 'default', type='bool', default=1))
1038n/a ##currently set theme
1039n/a currentOption = idleConf.CurrentTheme()
1040n/a ##load available theme option menus
1041n/a if self.themeIsBuiltin.get(): #default theme selected
1042n/a itemList = idleConf.GetSectionList('default', 'highlight')
1043n/a itemList.sort()
1044n/a self.optMenuThemeBuiltin.SetMenu(itemList, currentOption)
1045n/a itemList = idleConf.GetSectionList('user', 'highlight')
1046n/a itemList.sort()
1047n/a if not itemList:
1048n/a self.radioThemeCustom.config(state=DISABLED)
1049n/a self.customTheme.set('- no custom themes -')
1050n/a else:
1051n/a self.optMenuThemeCustom.SetMenu(itemList, itemList[0])
1052n/a else: #user theme selected
1053n/a itemList = idleConf.GetSectionList('user', 'highlight')
1054n/a itemList.sort()
1055n/a self.optMenuThemeCustom.SetMenu(itemList, currentOption)
1056n/a itemList = idleConf.GetSectionList('default', 'highlight')
1057n/a itemList.sort()
1058n/a self.optMenuThemeBuiltin.SetMenu(itemList, itemList[0])
1059n/a self.SetThemeType()
1060n/a ##load theme element option menu
1061n/a themeNames = list(self.themeElements.keys())
1062n/a themeNames.sort(key=lambda x: self.themeElements[x][1])
1063n/a self.optMenuHighlightTarget.SetMenu(themeNames, themeNames[0])
1064n/a self.PaintThemeSample()
1065n/a self.SetHighlightTarget()
1066n/a
1067n/a def LoadKeyCfg(self):
1068n/a ##current keys type radiobutton
1069n/a self.keysAreBuiltin.set(idleConf.GetOption(
1070n/a 'main', 'Keys', 'default', type='bool', default=1))
1071n/a ##currently set keys
1072n/a currentOption = idleConf.CurrentKeys()
1073n/a ##load available keyset option menus
1074n/a if self.keysAreBuiltin.get(): #default theme selected
1075n/a itemList = idleConf.GetSectionList('default', 'keys')
1076n/a itemList.sort()
1077n/a self.optMenuKeysBuiltin.SetMenu(itemList, currentOption)
1078n/a itemList = idleConf.GetSectionList('user', 'keys')
1079n/a itemList.sort()
1080n/a if not itemList:
1081n/a self.radioKeysCustom.config(state=DISABLED)
1082n/a self.customKeys.set('- no custom keys -')
1083n/a else:
1084n/a self.optMenuKeysCustom.SetMenu(itemList, itemList[0])
1085n/a else: #user key set selected
1086n/a itemList = idleConf.GetSectionList('user', 'keys')
1087n/a itemList.sort()
1088n/a self.optMenuKeysCustom.SetMenu(itemList, currentOption)
1089n/a itemList = idleConf.GetSectionList('default', 'keys')
1090n/a itemList.sort()
1091n/a self.optMenuKeysBuiltin.SetMenu(itemList, idleConf.default_keys())
1092n/a self.SetKeysType()
1093n/a ##load keyset element list
1094n/a keySetName = idleConf.CurrentKeys()
1095n/a self.LoadKeysList(keySetName)
1096n/a
1097n/a def LoadGeneralCfg(self):
1098n/a #startup state
1099n/a self.startupEdit.set(idleConf.GetOption(
1100n/a 'main', 'General', 'editor-on-startup', default=1, type='bool'))
1101n/a #autosave state
1102n/a self.autoSave.set(idleConf.GetOption(
1103n/a 'main', 'General', 'autosave', default=0, type='bool'))
1104n/a #initial window size
1105n/a self.winWidth.set(idleConf.GetOption(
1106n/a 'main', 'EditorWindow', 'width', type='int'))
1107n/a self.winHeight.set(idleConf.GetOption(
1108n/a 'main', 'EditorWindow', 'height', type='int'))
1109n/a # default source encoding
1110n/a self.encoding.set(idleConf.GetOption(
1111n/a 'main', 'EditorWindow', 'encoding', default='none'))
1112n/a # additional help sources
1113n/a self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
1114n/a for helpItem in self.userHelpList:
1115n/a self.listHelp.insert(END, helpItem[0])
1116n/a self.SetHelpListButtonStates()
1117n/a
1118n/a def LoadConfigs(self):
1119n/a """
1120n/a load configuration from default and user config files and populate
1121n/a the widgets on the config dialog pages.
1122n/a """
1123n/a ### fonts / tabs page
1124n/a self.LoadFontCfg()
1125n/a self.LoadTabCfg()
1126n/a ### highlighting page
1127n/a self.LoadThemeCfg()
1128n/a ### keys page
1129n/a self.LoadKeyCfg()
1130n/a ### general page
1131n/a self.LoadGeneralCfg()
1132n/a # note: extension page handled separately
1133n/a
1134n/a def SaveNewKeySet(self, keySetName, keySet):
1135n/a """
1136n/a save a newly created core key set.
1137n/a keySetName - string, the name of the new key set
1138n/a keySet - dictionary containing the new key set
1139n/a """
1140n/a if not idleConf.userCfg['keys'].has_section(keySetName):
1141n/a idleConf.userCfg['keys'].add_section(keySetName)
1142n/a for event in keySet:
1143n/a value = keySet[event]
1144n/a idleConf.userCfg['keys'].SetOption(keySetName, event, value)
1145n/a
1146n/a def SaveNewTheme(self, themeName, theme):
1147n/a """
1148n/a save a newly created theme.
1149n/a themeName - string, the name of the new theme
1150n/a theme - dictionary containing the new theme
1151n/a """
1152n/a if not idleConf.userCfg['highlight'].has_section(themeName):
1153n/a idleConf.userCfg['highlight'].add_section(themeName)
1154n/a for element in theme:
1155n/a value = theme[element]
1156n/a idleConf.userCfg['highlight'].SetOption(themeName, element, value)
1157n/a
1158n/a def SetUserValue(self, configType, section, item, value):
1159n/a if idleConf.defaultCfg[configType].has_option(section, item):
1160n/a if idleConf.defaultCfg[configType].Get(section, item) == value:
1161n/a #the setting equals a default setting, remove it from user cfg
1162n/a return idleConf.userCfg[configType].RemoveOption(section, item)
1163n/a #if we got here set the option
1164n/a return idleConf.userCfg[configType].SetOption(section, item, value)
1165n/a
1166n/a def SaveAllChangedConfigs(self):
1167n/a "Save configuration changes to the user config file."
1168n/a idleConf.userCfg['main'].Save()
1169n/a for configType in self.changedItems:
1170n/a cfgTypeHasChanges = False
1171n/a for section in self.changedItems[configType]:
1172n/a if section == 'HelpFiles':
1173n/a #this section gets completely replaced
1174n/a idleConf.userCfg['main'].remove_section('HelpFiles')
1175n/a cfgTypeHasChanges = True
1176n/a for item in self.changedItems[configType][section]:
1177n/a value = self.changedItems[configType][section][item]
1178n/a if self.SetUserValue(configType, section, item, value):
1179n/a cfgTypeHasChanges = True
1180n/a if cfgTypeHasChanges:
1181n/a idleConf.userCfg[configType].Save()
1182n/a for configType in ['keys', 'highlight']:
1183n/a # save these even if unchanged!
1184n/a idleConf.userCfg[configType].Save()
1185n/a self.ResetChangedItems() #clear the changed items dict
1186n/a self.save_all_changed_extensions() # uses a different mechanism
1187n/a
1188n/a def DeactivateCurrentConfig(self):
1189n/a #Before a config is saved, some cleanup of current
1190n/a #config must be done - remove the previous keybindings
1191n/a winInstances = self.parent.instance_dict.keys()
1192n/a for instance in winInstances:
1193n/a instance.RemoveKeybindings()
1194n/a
1195n/a def ActivateConfigChanges(self):
1196n/a "Dynamically apply configuration changes"
1197n/a winInstances = self.parent.instance_dict.keys()
1198n/a for instance in winInstances:
1199n/a instance.ResetColorizer()
1200n/a instance.ResetFont()
1201n/a instance.set_notabs_indentwidth()
1202n/a instance.ApplyKeybindings()
1203n/a instance.reset_help_menu_entries()
1204n/a
1205n/a def Cancel(self):
1206n/a self.destroy()
1207n/a
1208n/a def Ok(self):
1209n/a self.Apply()
1210n/a self.destroy()
1211n/a
1212n/a def Apply(self):
1213n/a self.DeactivateCurrentConfig()
1214n/a self.SaveAllChangedConfigs()
1215n/a self.ActivateConfigChanges()
1216n/a
1217n/a def Help(self):
1218n/a page = self.tabPages._current_page
1219n/a view_text(self, title='Help for IDLE preferences',
1220n/a text=help_common+help_pages.get(page, ''))
1221n/a
1222n/a def CreatePageExtensions(self):
1223n/a """Part of the config dialog used for configuring IDLE extensions.
1224n/a
1225n/a This code is generic - it works for any and all IDLE extensions.
1226n/a
1227n/a IDLE extensions save their configuration options using idleConf.
1228n/a This code reads the current configuration using idleConf, supplies a
1229n/a GUI interface to change the configuration values, and saves the
1230n/a changes using idleConf.
1231n/a
1232n/a Not all changes take effect immediately - some may require restarting IDLE.
1233n/a This depends on each extension's implementation.
1234n/a
1235n/a All values are treated as text, and it is up to the user to supply
1236n/a reasonable values. The only exception to this are the 'enable*' options,
1237n/a which are boolean, and can be toggled with a True/False button.
1238n/a """
1239n/a parent = self.parent
1240n/a frame = self.tabPages.pages['Extensions'].frame
1241n/a self.ext_defaultCfg = idleConf.defaultCfg['extensions']
1242n/a self.ext_userCfg = idleConf.userCfg['extensions']
1243n/a self.is_int = self.register(is_int)
1244n/a self.load_extensions()
1245n/a # create widgets - a listbox shows all available extensions, with the
1246n/a # controls for the extension selected in the listbox to the right
1247n/a self.extension_names = StringVar(self)
1248n/a frame.rowconfigure(0, weight=1)
1249n/a frame.columnconfigure(2, weight=1)
1250n/a self.extension_list = Listbox(frame, listvariable=self.extension_names,
1251n/a selectmode='browse')
1252n/a self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
1253n/a scroll = Scrollbar(frame, command=self.extension_list.yview)
1254n/a self.extension_list.yscrollcommand=scroll.set
1255n/a self.details_frame = LabelFrame(frame, width=250, height=250)
1256n/a self.extension_list.grid(column=0, row=0, sticky='nws')
1257n/a scroll.grid(column=1, row=0, sticky='ns')
1258n/a self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
1259n/a frame.configure(padx=10, pady=10)
1260n/a self.config_frame = {}
1261n/a self.current_extension = None
1262n/a
1263n/a self.outerframe = self # TEMPORARY
1264n/a self.tabbed_page_set = self.extension_list # TEMPORARY
1265n/a
1266n/a # create the frame holding controls for each extension
1267n/a ext_names = ''
1268n/a for ext_name in sorted(self.extensions):
1269n/a self.create_extension_frame(ext_name)
1270n/a ext_names = ext_names + '{' + ext_name + '} '
1271n/a self.extension_names.set(ext_names)
1272n/a self.extension_list.selection_set(0)
1273n/a self.extension_selected(None)
1274n/a
1275n/a def load_extensions(self):
1276n/a "Fill self.extensions with data from the default and user configs."
1277n/a self.extensions = {}
1278n/a for ext_name in idleConf.GetExtensions(active_only=False):
1279n/a self.extensions[ext_name] = []
1280n/a
1281n/a for ext_name in self.extensions:
1282n/a opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
1283n/a
1284n/a # bring 'enable' options to the beginning of the list
1285n/a enables = [opt_name for opt_name in opt_list
1286n/a if opt_name.startswith('enable')]
1287n/a for opt_name in enables:
1288n/a opt_list.remove(opt_name)
1289n/a opt_list = enables + opt_list
1290n/a
1291n/a for opt_name in opt_list:
1292n/a def_str = self.ext_defaultCfg.Get(
1293n/a ext_name, opt_name, raw=True)
1294n/a try:
1295n/a def_obj = {'True':True, 'False':False}[def_str]
1296n/a opt_type = 'bool'
1297n/a except KeyError:
1298n/a try:
1299n/a def_obj = int(def_str)
1300n/a opt_type = 'int'
1301n/a except ValueError:
1302n/a def_obj = def_str
1303n/a opt_type = None
1304n/a try:
1305n/a value = self.ext_userCfg.Get(
1306n/a ext_name, opt_name, type=opt_type, raw=True,
1307n/a default=def_obj)
1308n/a except ValueError: # Need this until .Get fixed
1309n/a value = def_obj # bad values overwritten by entry
1310n/a var = StringVar(self)
1311n/a var.set(str(value))
1312n/a
1313n/a self.extensions[ext_name].append({'name': opt_name,
1314n/a 'type': opt_type,
1315n/a 'default': def_str,
1316n/a 'value': value,
1317n/a 'var': var,
1318n/a })
1319n/a
1320n/a def extension_selected(self, event):
1321n/a newsel = self.extension_list.curselection()
1322n/a if newsel:
1323n/a newsel = self.extension_list.get(newsel)
1324n/a if newsel is None or newsel != self.current_extension:
1325n/a if self.current_extension:
1326n/a self.details_frame.config(text='')
1327n/a self.config_frame[self.current_extension].grid_forget()
1328n/a self.current_extension = None
1329n/a if newsel:
1330n/a self.details_frame.config(text=newsel)
1331n/a self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
1332n/a self.current_extension = newsel
1333n/a
1334n/a def create_extension_frame(self, ext_name):
1335n/a """Create a frame holding the widgets to configure one extension"""
1336n/a f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
1337n/a self.config_frame[ext_name] = f
1338n/a entry_area = f.interior
1339n/a # create an entry for each configuration option
1340n/a for row, opt in enumerate(self.extensions[ext_name]):
1341n/a # create a row with a label and entry/checkbutton
1342n/a label = Label(entry_area, text=opt['name'])
1343n/a label.grid(row=row, column=0, sticky=NW)
1344n/a var = opt['var']
1345n/a if opt['type'] == 'bool':
1346n/a Checkbutton(entry_area, textvariable=var, variable=var,
1347n/a onvalue='True', offvalue='False',
1348n/a indicatoron=FALSE, selectcolor='', width=8
1349n/a ).grid(row=row, column=1, sticky=W, padx=7)
1350n/a elif opt['type'] == 'int':
1351n/a Entry(entry_area, textvariable=var, validate='key',
1352n/a validatecommand=(self.is_int, '%P')
1353n/a ).grid(row=row, column=1, sticky=NSEW, padx=7)
1354n/a
1355n/a else:
1356n/a Entry(entry_area, textvariable=var
1357n/a ).grid(row=row, column=1, sticky=NSEW, padx=7)
1358n/a return
1359n/a
1360n/a def set_extension_value(self, section, opt):
1361n/a name = opt['name']
1362n/a default = opt['default']
1363n/a value = opt['var'].get().strip() or default
1364n/a opt['var'].set(value)
1365n/a # if self.defaultCfg.has_section(section):
1366n/a # Currently, always true; if not, indent to return
1367n/a if (value == default):
1368n/a return self.ext_userCfg.RemoveOption(section, name)
1369n/a # set the option
1370n/a return self.ext_userCfg.SetOption(section, name, value)
1371n/a
1372n/a def save_all_changed_extensions(self):
1373n/a """Save configuration changes to the user config file."""
1374n/a has_changes = False
1375n/a for ext_name in self.extensions:
1376n/a options = self.extensions[ext_name]
1377n/a for opt in options:
1378n/a if self.set_extension_value(ext_name, opt):
1379n/a has_changes = True
1380n/a if has_changes:
1381n/a self.ext_userCfg.Save()
1382n/a
1383n/a
1384n/ahelp_common = '''\
1385n/aWhen you click either the Apply or Ok buttons, settings in this
1386n/adialog that are different from IDLE's default are saved in
1387n/aa .idlerc directory in your home directory. Except as noted,
1388n/athese changes apply to all versions of IDLE installed on this
1389n/amachine. Some do not take affect until IDLE is restarted.
1390n/a[Cancel] only cancels changes made since the last save.
1391n/a'''
1392n/ahelp_pages = {
1393n/a 'Highlighting': '''
1394n/aHighlighting:
1395n/aThe IDLE Dark color theme is new in October 2015. It can only
1396n/abe used with older IDLE releases if it is saved as a custom
1397n/atheme, with a different name.
1398n/a''',
1399n/a 'Keys': '''
1400n/aKeys:
1401n/aThe IDLE Modern Unix key set is new in June 2016. It can only
1402n/abe used with older IDLE releases if it is saved as a custom
1403n/akey set, with a different name.
1404n/a''',
1405n/a}
1406n/a
1407n/a
1408n/adef is_int(s):
1409n/a "Return 's is blank or represents an int'"
1410n/a if not s:
1411n/a return True
1412n/a try:
1413n/a int(s)
1414n/a return True
1415n/a except ValueError:
1416n/a return False
1417n/a
1418n/a
1419n/aclass VerticalScrolledFrame(Frame):
1420n/a """A pure Tkinter vertically scrollable frame.
1421n/a
1422n/a * Use the 'interior' attribute to place widgets inside the scrollable frame
1423n/a * Construct and pack/place/grid normally
1424n/a * This frame only allows vertical scrolling
1425n/a """
1426n/a def __init__(self, parent, *args, **kw):
1427n/a Frame.__init__(self, parent, *args, **kw)
1428n/a
1429n/a # create a canvas object and a vertical scrollbar for scrolling it
1430n/a vscrollbar = Scrollbar(self, orient=VERTICAL)
1431n/a vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
1432n/a canvas = Canvas(self, bd=0, highlightthickness=0,
1433n/a yscrollcommand=vscrollbar.set, width=240)
1434n/a canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
1435n/a vscrollbar.config(command=canvas.yview)
1436n/a
1437n/a # reset the view
1438n/a canvas.xview_moveto(0)
1439n/a canvas.yview_moveto(0)
1440n/a
1441n/a # create a frame inside the canvas which will be scrolled with it
1442n/a self.interior = interior = Frame(canvas)
1443n/a interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
1444n/a
1445n/a # track changes to the canvas and frame width and sync them,
1446n/a # also updating the scrollbar
1447n/a def _configure_interior(event):
1448n/a # update the scrollbars to match the size of the inner frame
1449n/a size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
1450n/a canvas.config(scrollregion="0 0 %s %s" % size)
1451n/a interior.bind('<Configure>', _configure_interior)
1452n/a
1453n/a def _configure_canvas(event):
1454n/a if interior.winfo_reqwidth() != canvas.winfo_width():
1455n/a # update the inner frame's width to fill the canvas
1456n/a canvas.itemconfigure(interior_id, width=canvas.winfo_width())
1457n/a canvas.bind('<Configure>', _configure_canvas)
1458n/a
1459n/a return
1460n/a
1461n/a
1462n/aif __name__ == '__main__':
1463n/a import unittest
1464n/a unittest.main('idlelib.idle_test.test_configdialog',
1465n/a verbosity=2, exit=False)
1466n/a from idlelib.idle_test.htest import run
1467n/a run(ConfigDialog)