1"""IDLE Configuration Dialog: support user customization of IDLE by GUI
2
3Customize font faces, sizes, and colorization attributes.  Set indentation
4defaults.  Customize keybindings.  Colorization and keybindings can be
5saved as user defined sets.  Select startup options including shell/editor
6and default window size.  Define additional help sources.
7
8Note that tab width in IDLE is currently fixed at eight due to Tk issues.
9Refer to comments in EditorWindow autoindent code for details.
10
11"""
12from Tkinter import *
13import tkMessageBox, tkColorChooser, tkFont
14import string
15
16from idlelib.configHandler import idleConf
17from idlelib.dynOptionMenuWidget import DynOptionMenu
18from idlelib.tabbedpages import TabbedPageSet
19from idlelib.keybindingDialog import GetKeysDialog
20from idlelib.configSectionNameDialog import GetCfgSectionNameDialog
21from idlelib.configHelpSourceEdit import GetHelpSourceDialog
22from idlelib import macosxSupport
23
24class ConfigDialog(Toplevel):
25
26    def __init__(self,parent,title):
27        Toplevel.__init__(self, parent)
28        self.wm_withdraw()
29
30        self.configure(borderwidth=5)
31        self.title('IDLE Preferences')
32        self.geometry("+%d+%d" % (parent.winfo_rootx()+20,
33                parent.winfo_rooty()+30))
34        #Theme Elements. Each theme element key is its display name.
35        #The first value of the tuple is the sample area tag name.
36        #The second value is the display name list sort index.
37        self.themeElements={'Normal Text':('normal','00'),
38            'Python Keywords':('keyword','01'),
39            'Python Definitions':('definition','02'),
40            'Python Builtins':('builtin', '03'),
41            'Python Comments':('comment','04'),
42            'Python Strings':('string','05'),
43            'Selected Text':('hilite','06'),
44            'Found Text':('hit','07'),
45            'Cursor':('cursor','08'),
46            'Error Text':('error','09'),
47            'Shell Normal Text':('console','10'),
48            'Shell Stdout Text':('stdout','11'),
49            'Shell Stderr Text':('stderr','12'),
50            }
51        self.ResetChangedItems() #load initial values in changed items dict
52        self.CreateWidgets()
53        self.resizable(height=FALSE,width=FALSE)
54        self.transient(parent)
55        self.grab_set()
56        self.protocol("WM_DELETE_WINDOW", self.Cancel)
57        self.parent = parent
58        self.tabPages.focus_set()
59        #key bindings for this dialog
60        #self.bind('<Escape>',self.Cancel) #dismiss dialog, no save
61        #self.bind('<Alt-a>',self.Apply) #apply changes, save
62        #self.bind('<F1>',self.Help) #context help
63        self.LoadConfigs()
64        self.AttachVarCallbacks() #avoid callbacks during LoadConfigs
65
66        self.wm_deiconify()
67        self.wait_window()
68
69    def CreateWidgets(self):
70        self.tabPages = TabbedPageSet(self,
71                page_names=['Fonts/Tabs','Highlighting','Keys','General'])
72        frameActionButtons = Frame(self,pady=2)
73        #action buttons
74        if macosxSupport.runningAsOSXApp():
75            # Changing the default padding on OSX results in unreadable
76            # text in the buttons
77            paddingArgs={}
78        else:
79            paddingArgs={'padx':6, 'pady':3}
80
81        self.buttonHelp = Button(frameActionButtons,text='Help',
82                command=self.Help,takefocus=FALSE,
83                **paddingArgs)
84        self.buttonOk = Button(frameActionButtons,text='Ok',
85                command=self.Ok,takefocus=FALSE,
86                **paddingArgs)
87        self.buttonApply = Button(frameActionButtons,text='Apply',
88                command=self.Apply,takefocus=FALSE,
89                **paddingArgs)
90        self.buttonCancel = Button(frameActionButtons,text='Cancel',
91                command=self.Cancel,takefocus=FALSE,
92                **paddingArgs)
93        self.CreatePageFontTab()
94        self.CreatePageHighlight()
95        self.CreatePageKeys()
96        self.CreatePageGeneral()
97        self.buttonHelp.pack(side=RIGHT,padx=5)
98        self.buttonOk.pack(side=LEFT,padx=5)
99        self.buttonApply.pack(side=LEFT,padx=5)
100        self.buttonCancel.pack(side=LEFT,padx=5)
101        frameActionButtons.pack(side=BOTTOM)
102        Frame(self, height=2, borderwidth=0).pack(side=BOTTOM)
103        self.tabPages.pack(side=TOP,expand=TRUE,fill=BOTH)
104
105    def CreatePageFontTab(self):
106        #tkVars
107        self.fontSize=StringVar(self)
108        self.fontBold=BooleanVar(self)
109        self.fontName=StringVar(self)
110        self.spaceNum=IntVar(self)
111        self.editFont=tkFont.Font(self,('courier',10,'normal'))
112        ##widget creation
113        #body frame
114        frame=self.tabPages.pages['Fonts/Tabs'].frame
115        #body section frames
116        frameFont=LabelFrame(frame,borderwidth=2,relief=GROOVE,
117                             text=' Base Editor Font ')
118        frameIndent=LabelFrame(frame,borderwidth=2,relief=GROOVE,
119                               text=' Indentation Width ')
120        #frameFont
121        frameFontName=Frame(frameFont)
122        frameFontParam=Frame(frameFont)
123        labelFontNameTitle=Label(frameFontName,justify=LEFT,
124                text='Font Face :')
125        self.listFontName=Listbox(frameFontName,height=5,takefocus=FALSE,
126                exportselection=FALSE)
127        self.listFontName.bind('<ButtonRelease-1>',self.OnListFontButtonRelease)
128        scrollFont=Scrollbar(frameFontName)
129        scrollFont.config(command=self.listFontName.yview)
130        self.listFontName.config(yscrollcommand=scrollFont.set)
131        labelFontSizeTitle=Label(frameFontParam,text='Size :')
132        self.optMenuFontSize=DynOptionMenu(frameFontParam,self.fontSize,None,
133            command=self.SetFontSample)
134        checkFontBold=Checkbutton(frameFontParam,variable=self.fontBold,
135            onvalue=1,offvalue=0,text='Bold',command=self.SetFontSample)
136        frameFontSample=Frame(frameFont,relief=SOLID,borderwidth=1)
137        self.labelFontSample=Label(frameFontSample,
138                text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]',
139                justify=LEFT,font=self.editFont)
140        #frameIndent
141        frameIndentSize=Frame(frameIndent)
142        labelSpaceNumTitle=Label(frameIndentSize, justify=LEFT,
143                                 text='Python Standard: 4 Spaces!')
144        self.scaleSpaceNum=Scale(frameIndentSize, variable=self.spaceNum,
145                                 orient='horizontal',
146                                 tickinterval=2, from_=2, to=16)
147        #widget packing
148        #body
149        frameFont.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
150        frameIndent.pack(side=LEFT,padx=5,pady=5,fill=Y)
151        #frameFont
152        frameFontName.pack(side=TOP,padx=5,pady=5,fill=X)
153        frameFontParam.pack(side=TOP,padx=5,pady=5,fill=X)
154        labelFontNameTitle.pack(side=TOP,anchor=W)
155        self.listFontName.pack(side=LEFT,expand=TRUE,fill=X)
156        scrollFont.pack(side=LEFT,fill=Y)
157        labelFontSizeTitle.pack(side=LEFT,anchor=W)
158        self.optMenuFontSize.pack(side=LEFT,anchor=W)
159        checkFontBold.pack(side=LEFT,anchor=W,padx=20)
160        frameFontSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
161        self.labelFontSample.pack(expand=TRUE,fill=BOTH)
162        #frameIndent
163        frameIndentSize.pack(side=TOP,fill=X)
164        labelSpaceNumTitle.pack(side=TOP,anchor=W,padx=5)
165        self.scaleSpaceNum.pack(side=TOP,padx=5,fill=X)
166        return frame
167
168    def CreatePageHighlight(self):
169        self.builtinTheme=StringVar(self)
170        self.customTheme=StringVar(self)
171        self.fgHilite=BooleanVar(self)
172        self.colour=StringVar(self)
173        self.fontName=StringVar(self)
174        self.themeIsBuiltin=BooleanVar(self)
175        self.highlightTarget=StringVar(self)
176        ##widget creation
177        #body frame
178        frame=self.tabPages.pages['Highlighting'].frame
179        #body section frames
180        frameCustom=LabelFrame(frame,borderwidth=2,relief=GROOVE,
181                               text=' Custom Highlighting ')
182        frameTheme=LabelFrame(frame,borderwidth=2,relief=GROOVE,
183                              text=' Highlighting Theme ')
184        #frameCustom
185        self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1,
186            font=('courier',12,''),cursor='hand2',width=21,height=11,
187            takefocus=FALSE,highlightthickness=0,wrap=NONE)
188        text=self.textHighlightSample
189        text.bind('<Double-Button-1>',lambda e: 'break')
190        text.bind('<B1-Motion>',lambda e: 'break')
191        textAndTags=(('#you can click here','comment'),('\n','normal'),
192            ('#to choose items','comment'),('\n','normal'),('def','keyword'),
193            (' ','normal'),('func','definition'),('(param):','normal'),
194            ('\n  ','normal'),('"""string"""','string'),('\n  var0 = ','normal'),
195            ("'string'",'string'),('\n  var1 = ','normal'),("'selected'",'hilite'),
196            ('\n  var2 = ','normal'),("'found'",'hit'),
197            ('\n  var3 = ','normal'),('list', 'builtin'), ('(','normal'),
198            ('None', 'builtin'),(')\n\n','normal'),
199            (' error ','error'),(' ','normal'),('cursor |','cursor'),
200            ('\n ','normal'),('shell','console'),(' ','normal'),('stdout','stdout'),
201            (' ','normal'),('stderr','stderr'),('\n','normal'))
202        for txTa in textAndTags:
203            text.insert(END,txTa[0],txTa[1])
204        for element in self.themeElements.keys():
205            text.tag_bind(self.themeElements[element][0],'<ButtonPress-1>',
206                lambda event,elem=element: event.widget.winfo_toplevel()
207                .highlightTarget.set(elem))
208        text.config(state=DISABLED)
209        self.frameColourSet=Frame(frameCustom,relief=SOLID,borderwidth=1)
210        frameFgBg=Frame(frameCustom)
211        buttonSetColour=Button(self.frameColourSet,text='Choose Colour for :',
212            command=self.GetColour,highlightthickness=0)
213        self.optMenuHighlightTarget=DynOptionMenu(self.frameColourSet,
214            self.highlightTarget,None,highlightthickness=0)#,command=self.SetHighlightTargetBinding
215        self.radioFg=Radiobutton(frameFgBg,variable=self.fgHilite,
216            value=1,text='Foreground',command=self.SetColourSampleBinding)
217        self.radioBg=Radiobutton(frameFgBg,variable=self.fgHilite,
218            value=0,text='Background',command=self.SetColourSampleBinding)
219        self.fgHilite.set(1)
220        buttonSaveCustomTheme=Button(frameCustom,
221            text='Save as New Custom Theme',command=self.SaveAsNewTheme)
222        #frameTheme
223        labelTypeTitle=Label(frameTheme,text='Select : ')
224        self.radioThemeBuiltin=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
225            value=1,command=self.SetThemeType,text='a Built-in Theme')
226        self.radioThemeCustom=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
227            value=0,command=self.SetThemeType,text='a Custom Theme')
228        self.optMenuThemeBuiltin=DynOptionMenu(frameTheme,
229            self.builtinTheme,None,command=None)
230        self.optMenuThemeCustom=DynOptionMenu(frameTheme,
231            self.customTheme,None,command=None)
232        self.buttonDeleteCustomTheme=Button(frameTheme,text='Delete Custom Theme',
233                command=self.DeleteCustomTheme)
234        ##widget packing
235        #body
236        frameCustom.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
237        frameTheme.pack(side=LEFT,padx=5,pady=5,fill=Y)
238        #frameCustom
239        self.frameColourSet.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=X)
240        frameFgBg.pack(side=TOP,padx=5,pady=0)
241        self.textHighlightSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,
242            fill=BOTH)
243        buttonSetColour.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=4)
244        self.optMenuHighlightTarget.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=3)
245        self.radioFg.pack(side=LEFT,anchor=E)
246        self.radioBg.pack(side=RIGHT,anchor=W)
247        buttonSaveCustomTheme.pack(side=BOTTOM,fill=X,padx=5,pady=5)
248        #frameTheme
249        labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
250        self.radioThemeBuiltin.pack(side=TOP,anchor=W,padx=5)
251        self.radioThemeCustom.pack(side=TOP,anchor=W,padx=5,pady=2)
252        self.optMenuThemeBuiltin.pack(side=TOP,fill=X,padx=5,pady=5)
253        self.optMenuThemeCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5)
254        self.buttonDeleteCustomTheme.pack(side=TOP,fill=X,padx=5,pady=5)
255        return frame
256
257    def CreatePageKeys(self):
258        #tkVars
259        self.bindingTarget=StringVar(self)
260        self.builtinKeys=StringVar(self)
261        self.customKeys=StringVar(self)
262        self.keysAreBuiltin=BooleanVar(self)
263        self.keyBinding=StringVar(self)
264        ##widget creation
265        #body frame
266        frame=self.tabPages.pages['Keys'].frame
267        #body section frames
268        frameCustom=LabelFrame(frame,borderwidth=2,relief=GROOVE,
269                               text=' Custom Key Bindings ')
270        frameKeySets=LabelFrame(frame,borderwidth=2,relief=GROOVE,
271                           text=' Key Set ')
272        #frameCustom
273        frameTarget=Frame(frameCustom)
274        labelTargetTitle=Label(frameTarget,text='Action - Key(s)')
275        scrollTargetY=Scrollbar(frameTarget)
276        scrollTargetX=Scrollbar(frameTarget,orient=HORIZONTAL)
277        self.listBindings=Listbox(frameTarget,takefocus=FALSE,
278                exportselection=FALSE)
279        self.listBindings.bind('<ButtonRelease-1>',self.KeyBindingSelected)
280        scrollTargetY.config(command=self.listBindings.yview)
281        scrollTargetX.config(command=self.listBindings.xview)
282        self.listBindings.config(yscrollcommand=scrollTargetY.set)
283        self.listBindings.config(xscrollcommand=scrollTargetX.set)
284        self.buttonNewKeys=Button(frameCustom,text='Get New Keys for Selection',
285            command=self.GetNewKeys,state=DISABLED)
286        #frameKeySets
287        frames = [Frame(frameKeySets, padx=2, pady=2, borderwidth=0)
288                  for i in range(2)]
289        self.radioKeysBuiltin=Radiobutton(frames[0],variable=self.keysAreBuiltin,
290            value=1,command=self.SetKeysType,text='Use a Built-in Key Set')
291        self.radioKeysCustom=Radiobutton(frames[0],variable=self.keysAreBuiltin,
292            value=0,command=self.SetKeysType,text='Use a Custom Key Set')
293        self.optMenuKeysBuiltin=DynOptionMenu(frames[0],
294            self.builtinKeys,None,command=None)
295        self.optMenuKeysCustom=DynOptionMenu(frames[0],
296            self.customKeys,None,command=None)
297        self.buttonDeleteCustomKeys=Button(frames[1],text='Delete Custom Key Set',
298                command=self.DeleteCustomKeys)
299        buttonSaveCustomKeys=Button(frames[1],
300                text='Save as New Custom Key Set',command=self.SaveAsNewKeySet)
301        ##widget packing
302        #body
303        frameCustom.pack(side=BOTTOM,padx=5,pady=5,expand=TRUE,fill=BOTH)
304        frameKeySets.pack(side=BOTTOM,padx=5,pady=5,fill=BOTH)
305        #frameCustom
306        self.buttonNewKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5)
307        frameTarget.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
308        #frame target
309        frameTarget.columnconfigure(0,weight=1)
310        frameTarget.rowconfigure(1,weight=1)
311        labelTargetTitle.grid(row=0,column=0,columnspan=2,sticky=W)
312        self.listBindings.grid(row=1,column=0,sticky=NSEW)
313        scrollTargetY.grid(row=1,column=1,sticky=NS)
314        scrollTargetX.grid(row=2,column=0,sticky=EW)
315        #frameKeySets
316        self.radioKeysBuiltin.grid(row=0, column=0, sticky=W+NS)
317        self.radioKeysCustom.grid(row=1, column=0, sticky=W+NS)
318        self.optMenuKeysBuiltin.grid(row=0, column=1, sticky=NSEW)
319        self.optMenuKeysCustom.grid(row=1, column=1, sticky=NSEW)
320        self.buttonDeleteCustomKeys.pack(side=LEFT,fill=X,expand=True,padx=2)
321        buttonSaveCustomKeys.pack(side=LEFT,fill=X,expand=True,padx=2)
322        frames[0].pack(side=TOP, fill=BOTH, expand=True)
323        frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
324        return frame
325
326    def CreatePageGeneral(self):
327        #tkVars
328        self.winWidth=StringVar(self)
329        self.winHeight=StringVar(self)
330        self.paraWidth=StringVar(self)
331        self.startupEdit=IntVar(self)
332        self.autoSave=IntVar(self)
333        self.encoding=StringVar(self)
334        self.userHelpBrowser=BooleanVar(self)
335        self.helpBrowser=StringVar(self)
336        #widget creation
337        #body
338        frame=self.tabPages.pages['General'].frame
339        #body section frames
340        frameRun=LabelFrame(frame,borderwidth=2,relief=GROOVE,
341                            text=' Startup Preferences ')
342        frameSave=LabelFrame(frame,borderwidth=2,relief=GROOVE,
343                             text=' Autosave Preferences ')
344        frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE)
345        frameParaSize=Frame(frame,borderwidth=2,relief=GROOVE)
346        frameEncoding=Frame(frame,borderwidth=2,relief=GROOVE)
347        frameHelp=LabelFrame(frame,borderwidth=2,relief=GROOVE,
348                             text=' Additional Help Sources ')
349        #frameRun
350        labelRunChoiceTitle=Label(frameRun,text='At Startup')
351        radioStartupEdit=Radiobutton(frameRun,variable=self.startupEdit,
352            value=1,command=self.SetKeysType,text="Open Edit Window")
353        radioStartupShell=Radiobutton(frameRun,variable=self.startupEdit,
354            value=0,command=self.SetKeysType,text='Open Shell Window')
355        #frameSave
356        labelRunSaveTitle=Label(frameSave,text='At Start of Run (F5)  ')
357        radioSaveAsk=Radiobutton(frameSave,variable=self.autoSave,
358            value=0,command=self.SetKeysType,text="Prompt to Save")
359        radioSaveAuto=Radiobutton(frameSave,variable=self.autoSave,
360            value=1,command=self.SetKeysType,text='No Prompt')
361        #frameWinSize
362        labelWinSizeTitle=Label(frameWinSize,text='Initial Window Size'+
363                '  (in characters)')
364        labelWinWidthTitle=Label(frameWinSize,text='Width')
365        entryWinWidth=Entry(frameWinSize,textvariable=self.winWidth,
366                width=3)
367        labelWinHeightTitle=Label(frameWinSize,text='Height')
368        entryWinHeight=Entry(frameWinSize,textvariable=self.winHeight,
369                width=3)
370        #paragraphFormatWidth
371        labelParaWidthTitle=Label(frameParaSize,text='Paragraph reformat'+
372                ' width (in characters)')
373        entryParaWidth=Entry(frameParaSize,textvariable=self.paraWidth,
374                width=3)
375        #frameEncoding
376        labelEncodingTitle=Label(frameEncoding,text="Default Source Encoding")
377        radioEncLocale=Radiobutton(frameEncoding,variable=self.encoding,
378            value="locale",text="Locale-defined")
379        radioEncUTF8=Radiobutton(frameEncoding,variable=self.encoding,
380            value="utf-8",text="UTF-8")
381        radioEncNone=Radiobutton(frameEncoding,variable=self.encoding,
382            value="none",text="None")
383        #frameHelp
384        frameHelpList=Frame(frameHelp)
385        frameHelpListButtons=Frame(frameHelpList)
386        scrollHelpList=Scrollbar(frameHelpList)
387        self.listHelp=Listbox(frameHelpList,height=5,takefocus=FALSE,
388                exportselection=FALSE)
389        scrollHelpList.config(command=self.listHelp.yview)
390        self.listHelp.config(yscrollcommand=scrollHelpList.set)
391        self.listHelp.bind('<ButtonRelease-1>',self.HelpSourceSelected)
392        self.buttonHelpListEdit=Button(frameHelpListButtons,text='Edit',
393                state=DISABLED,width=8,command=self.HelpListItemEdit)
394        self.buttonHelpListAdd=Button(frameHelpListButtons,text='Add',
395                width=8,command=self.HelpListItemAdd)
396        self.buttonHelpListRemove=Button(frameHelpListButtons,text='Remove',
397                state=DISABLED,width=8,command=self.HelpListItemRemove)
398        #widget packing
399        #body
400        frameRun.pack(side=TOP,padx=5,pady=5,fill=X)
401        frameSave.pack(side=TOP,padx=5,pady=5,fill=X)
402        frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X)
403        frameParaSize.pack(side=TOP,padx=5,pady=5,fill=X)
404        frameEncoding.pack(side=TOP,padx=5,pady=5,fill=X)
405        frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
406        #frameRun
407        labelRunChoiceTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
408        radioStartupShell.pack(side=RIGHT,anchor=W,padx=5,pady=5)
409        radioStartupEdit.pack(side=RIGHT,anchor=W,padx=5,pady=5)
410        #frameSave
411        labelRunSaveTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
412        radioSaveAuto.pack(side=RIGHT,anchor=W,padx=5,pady=5)
413        radioSaveAsk.pack(side=RIGHT,anchor=W,padx=5,pady=5)
414        #frameWinSize
415        labelWinSizeTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
416        entryWinHeight.pack(side=RIGHT,anchor=E,padx=10,pady=5)
417        labelWinHeightTitle.pack(side=RIGHT,anchor=E,pady=5)
418        entryWinWidth.pack(side=RIGHT,anchor=E,padx=10,pady=5)
419        labelWinWidthTitle.pack(side=RIGHT,anchor=E,pady=5)
420        #paragraphFormatWidth
421        labelParaWidthTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
422        entryParaWidth.pack(side=RIGHT,anchor=E,padx=10,pady=5)
423        #frameEncoding
424        labelEncodingTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
425        radioEncNone.pack(side=RIGHT,anchor=E,pady=5)
426        radioEncUTF8.pack(side=RIGHT,anchor=E,pady=5)
427        radioEncLocale.pack(side=RIGHT,anchor=E,pady=5)
428        #frameHelp
429        frameHelpListButtons.pack(side=RIGHT,padx=5,pady=5,fill=Y)
430        frameHelpList.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
431        scrollHelpList.pack(side=RIGHT,anchor=W,fill=Y)
432        self.listHelp.pack(side=LEFT,anchor=E,expand=TRUE,fill=BOTH)
433        self.buttonHelpListEdit.pack(side=TOP,anchor=W,pady=5)
434        self.buttonHelpListAdd.pack(side=TOP,anchor=W)
435        self.buttonHelpListRemove.pack(side=TOP,anchor=W,pady=5)
436        return frame
437
438    def AttachVarCallbacks(self):
439        self.fontSize.trace_variable('w',self.VarChanged_fontSize)
440        self.fontName.trace_variable('w',self.VarChanged_fontName)
441        self.fontBold.trace_variable('w',self.VarChanged_fontBold)
442        self.spaceNum.trace_variable('w',self.VarChanged_spaceNum)
443        self.colour.trace_variable('w',self.VarChanged_colour)
444        self.builtinTheme.trace_variable('w',self.VarChanged_builtinTheme)
445        self.customTheme.trace_variable('w',self.VarChanged_customTheme)
446        self.themeIsBuiltin.trace_variable('w',self.VarChanged_themeIsBuiltin)
447        self.highlightTarget.trace_variable('w',self.VarChanged_highlightTarget)
448        self.keyBinding.trace_variable('w',self.VarChanged_keyBinding)
449        self.builtinKeys.trace_variable('w',self.VarChanged_builtinKeys)
450        self.customKeys.trace_variable('w',self.VarChanged_customKeys)
451        self.keysAreBuiltin.trace_variable('w',self.VarChanged_keysAreBuiltin)
452        self.winWidth.trace_variable('w',self.VarChanged_winWidth)
453        self.winHeight.trace_variable('w',self.VarChanged_winHeight)
454        self.paraWidth.trace_variable('w',self.VarChanged_paraWidth)
455        self.startupEdit.trace_variable('w',self.VarChanged_startupEdit)
456        self.autoSave.trace_variable('w',self.VarChanged_autoSave)
457        self.encoding.trace_variable('w',self.VarChanged_encoding)
458
459    def VarChanged_fontSize(self,*params):
460        value=self.fontSize.get()
461        self.AddChangedItem('main','EditorWindow','font-size',value)
462
463    def VarChanged_fontName(self,*params):
464        value=self.fontName.get()
465        self.AddChangedItem('main','EditorWindow','font',value)
466
467    def VarChanged_fontBold(self,*params):
468        value=self.fontBold.get()
469        self.AddChangedItem('main','EditorWindow','font-bold',value)
470
471    def VarChanged_spaceNum(self,*params):
472        value=self.spaceNum.get()
473        self.AddChangedItem('main','Indent','num-spaces',value)
474
475    def VarChanged_colour(self,*params):
476        self.OnNewColourSet()
477
478    def VarChanged_builtinTheme(self,*params):
479        value=self.builtinTheme.get()
480        self.AddChangedItem('main','Theme','name',value)
481        self.PaintThemeSample()
482
483    def VarChanged_customTheme(self,*params):
484        value=self.customTheme.get()
485        if value != '- no custom themes -':
486            self.AddChangedItem('main','Theme','name',value)
487            self.PaintThemeSample()
488
489    def VarChanged_themeIsBuiltin(self,*params):
490        value=self.themeIsBuiltin.get()
491        self.AddChangedItem('main','Theme','default',value)
492        if value:
493            self.VarChanged_builtinTheme()
494        else:
495            self.VarChanged_customTheme()
496
497    def VarChanged_highlightTarget(self,*params):
498        self.SetHighlightTarget()
499
500    def VarChanged_keyBinding(self,*params):
501        value=self.keyBinding.get()
502        keySet=self.customKeys.get()
503        event=self.listBindings.get(ANCHOR).split()[0]
504        if idleConf.IsCoreBinding(event):
505            #this is a core keybinding
506            self.AddChangedItem('keys',keySet,event,value)
507        else: #this is an extension key binding
508            extName=idleConf.GetExtnNameForEvent(event)
509            extKeybindSection=extName+'_cfgBindings'
510            self.AddChangedItem('extensions',extKeybindSection,event,value)
511
512    def VarChanged_builtinKeys(self,*params):
513        value=self.builtinKeys.get()
514        self.AddChangedItem('main','Keys','name',value)
515        self.LoadKeysList(value)
516
517    def VarChanged_customKeys(self,*params):
518        value=self.customKeys.get()
519        if value != '- no custom keys -':
520            self.AddChangedItem('main','Keys','name',value)
521            self.LoadKeysList(value)
522
523    def VarChanged_keysAreBuiltin(self,*params):
524        value=self.keysAreBuiltin.get()
525        self.AddChangedItem('main','Keys','default',value)
526        if value:
527            self.VarChanged_builtinKeys()
528        else:
529            self.VarChanged_customKeys()
530
531    def VarChanged_winWidth(self,*params):
532        value=self.winWidth.get()
533        self.AddChangedItem('main','EditorWindow','width',value)
534
535    def VarChanged_winHeight(self,*params):
536        value=self.winHeight.get()
537        self.AddChangedItem('main','EditorWindow','height',value)
538
539    def VarChanged_paraWidth(self,*params):
540        value=self.paraWidth.get()
541        self.AddChangedItem('main','FormatParagraph','paragraph',value)
542
543    def VarChanged_startupEdit(self,*params):
544        value=self.startupEdit.get()
545        self.AddChangedItem('main','General','editor-on-startup',value)
546
547    def VarChanged_autoSave(self,*params):
548        value=self.autoSave.get()
549        self.AddChangedItem('main','General','autosave',value)
550
551    def VarChanged_encoding(self,*params):
552        value=self.encoding.get()
553        self.AddChangedItem('main','EditorWindow','encoding',value)
554
555    def ResetChangedItems(self):
556        #When any config item is changed in this dialog, an entry
557        #should be made in the relevant section (config type) of this
558        #dictionary. The key should be the config file section name and the
559        #value a dictionary, whose key:value pairs are item=value pairs for
560        #that config file section.
561        self.changedItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
562
563    def AddChangedItem(self,type,section,item,value):
564        value=str(value) #make sure we use a string
565        if section not in self.changedItems[type]:
566            self.changedItems[type][section]={}
567        self.changedItems[type][section][item]=value
568
569    def GetDefaultItems(self):
570        dItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
571        for configType in dItems.keys():
572            sections=idleConf.GetSectionList('default',configType)
573            for section in sections:
574                dItems[configType][section]={}
575                options=idleConf.defaultCfg[configType].GetOptionList(section)
576                for option in options:
577                    dItems[configType][section][option]=(
578                            idleConf.defaultCfg[configType].Get(section,option))
579        return dItems
580
581    def SetThemeType(self):
582        if self.themeIsBuiltin.get():
583            self.optMenuThemeBuiltin.config(state=NORMAL)
584            self.optMenuThemeCustom.config(state=DISABLED)
585            self.buttonDeleteCustomTheme.config(state=DISABLED)
586        else:
587            self.optMenuThemeBuiltin.config(state=DISABLED)
588            self.radioThemeCustom.config(state=NORMAL)
589            self.optMenuThemeCustom.config(state=NORMAL)
590            self.buttonDeleteCustomTheme.config(state=NORMAL)
591
592    def SetKeysType(self):
593        if self.keysAreBuiltin.get():
594            self.optMenuKeysBuiltin.config(state=NORMAL)
595            self.optMenuKeysCustom.config(state=DISABLED)
596            self.buttonDeleteCustomKeys.config(state=DISABLED)
597        else:
598            self.optMenuKeysBuiltin.config(state=DISABLED)
599            self.radioKeysCustom.config(state=NORMAL)
600            self.optMenuKeysCustom.config(state=NORMAL)
601            self.buttonDeleteCustomKeys.config(state=NORMAL)
602
603    def GetNewKeys(self):
604        listIndex=self.listBindings.index(ANCHOR)
605        binding=self.listBindings.get(listIndex)
606        bindName=binding.split()[0] #first part, up to first space
607        if self.keysAreBuiltin.get():
608            currentKeySetName=self.builtinKeys.get()
609        else:
610            currentKeySetName=self.customKeys.get()
611        currentBindings=idleConf.GetCurrentKeySet()
612        if currentKeySetName in self.changedItems['keys'].keys(): #unsaved changes
613            keySetChanges=self.changedItems['keys'][currentKeySetName]
614            for event in keySetChanges.keys():
615                currentBindings[event]=keySetChanges[event].split()
616        currentKeySequences=currentBindings.values()
617        newKeys=GetKeysDialog(self,'Get New Keys',bindName,
618                currentKeySequences).result
619        if newKeys: #new keys were specified
620            if self.keysAreBuiltin.get(): #current key set is a built-in
621                message=('Your changes will be saved as a new Custom Key Set. '+
622                        'Enter a name for your new Custom Key Set below.')
623                newKeySet=self.GetNewKeysName(message)
624                if not newKeySet: #user cancelled custom key set creation
625                    self.listBindings.select_set(listIndex)
626                    self.listBindings.select_anchor(listIndex)
627                    return
628                else: #create new custom key set based on previously active key set
629                    self.CreateNewKeySet(newKeySet)
630            self.listBindings.delete(listIndex)
631            self.listBindings.insert(listIndex,bindName+' - '+newKeys)
632            self.listBindings.select_set(listIndex)
633            self.listBindings.select_anchor(listIndex)
634            self.keyBinding.set(newKeys)
635        else:
636            self.listBindings.select_set(listIndex)
637            self.listBindings.select_anchor(listIndex)
638
639    def GetNewKeysName(self,message):
640        usedNames=(idleConf.GetSectionList('user','keys')+
641                idleConf.GetSectionList('default','keys'))
642        newKeySet=GetCfgSectionNameDialog(self,'New Custom Key Set',
643                message,usedNames).result
644        return newKeySet
645
646    def SaveAsNewKeySet(self):
647        newKeysName=self.GetNewKeysName('New Key Set Name:')
648        if newKeysName:
649            self.CreateNewKeySet(newKeysName)
650
651    def KeyBindingSelected(self,event):
652        self.buttonNewKeys.config(state=NORMAL)
653
654    def CreateNewKeySet(self,newKeySetName):
655        #creates new custom key set based on the previously active key set,
656        #and makes the new key set active
657        if self.keysAreBuiltin.get():
658            prevKeySetName=self.builtinKeys.get()
659        else:
660            prevKeySetName=self.customKeys.get()
661        prevKeys=idleConf.GetCoreKeys(prevKeySetName)
662        newKeys={}
663        for event in prevKeys.keys(): #add key set to changed items
664            eventName=event[2:-2] #trim off the angle brackets
665            binding=string.join(prevKeys[event])
666            newKeys[eventName]=binding
667        #handle any unsaved changes to prev key set
668        if prevKeySetName in self.changedItems['keys'].keys():
669            keySetChanges=self.changedItems['keys'][prevKeySetName]
670            for event in keySetChanges.keys():
671                newKeys[event]=keySetChanges[event]
672        #save the new theme
673        self.SaveNewKeySet(newKeySetName,newKeys)
674        #change gui over to the new key set
675        customKeyList=idleConf.GetSectionList('user','keys')
676        customKeyList.sort()
677        self.optMenuKeysCustom.SetMenu(customKeyList,newKeySetName)
678        self.keysAreBuiltin.set(0)
679        self.SetKeysType()
680
681    def LoadKeysList(self,keySetName):
682        reselect=0
683        newKeySet=0
684        if self.listBindings.curselection():
685            reselect=1
686            listIndex=self.listBindings.index(ANCHOR)
687        keySet=idleConf.GetKeySet(keySetName)
688        bindNames=keySet.keys()
689        bindNames.sort()
690        self.listBindings.delete(0,END)
691        for bindName in bindNames:
692            key=string.join(keySet[bindName]) #make key(s) into a string
693            bindName=bindName[2:-2] #trim off the angle brackets
694            if keySetName in self.changedItems['keys'].keys():
695                #handle any unsaved changes to this key set
696                if bindName in self.changedItems['keys'][keySetName].keys():
697                    key=self.changedItems['keys'][keySetName][bindName]
698            self.listBindings.insert(END, bindName+' - '+key)
699        if reselect:
700            self.listBindings.see(listIndex)
701            self.listBindings.select_set(listIndex)
702            self.listBindings.select_anchor(listIndex)
703
704    def DeleteCustomKeys(self):
705        keySetName=self.customKeys.get()
706        if not tkMessageBox.askyesno('Delete Key Set','Are you sure you wish '+
707                                     'to delete the key set %r ?' % (keySetName),
708                                     parent=self):
709            return
710        #remove key set from config
711        idleConf.userCfg['keys'].remove_section(keySetName)
712        if keySetName in self.changedItems['keys']:
713            del(self.changedItems['keys'][keySetName])
714        #write changes
715        idleConf.userCfg['keys'].Save()
716        #reload user key set list
717        itemList=idleConf.GetSectionList('user','keys')
718        itemList.sort()
719        if not itemList:
720            self.radioKeysCustom.config(state=DISABLED)
721            self.optMenuKeysCustom.SetMenu(itemList,'- no custom keys -')
722        else:
723            self.optMenuKeysCustom.SetMenu(itemList,itemList[0])
724        #revert to default key set
725        self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys','default'))
726        self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys','name'))
727        #user can't back out of these changes, they must be applied now
728        self.Apply()
729        self.SetKeysType()
730
731    def DeleteCustomTheme(self):
732        themeName=self.customTheme.get()
733        if not tkMessageBox.askyesno('Delete Theme','Are you sure you wish '+
734                                     'to delete the theme %r ?' % (themeName,),
735                                     parent=self):
736            return
737        #remove theme from config
738        idleConf.userCfg['highlight'].remove_section(themeName)
739        if themeName in self.changedItems['highlight']:
740            del(self.changedItems['highlight'][themeName])
741        #write changes
742        idleConf.userCfg['highlight'].Save()
743        #reload user theme list
744        itemList=idleConf.GetSectionList('user','highlight')
745        itemList.sort()
746        if not itemList:
747            self.radioThemeCustom.config(state=DISABLED)
748            self.optMenuThemeCustom.SetMenu(itemList,'- no custom themes -')
749        else:
750            self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
751        #revert to default theme
752        self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme','default'))
753        self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme','name'))
754        #user can't back out of these changes, they must be applied now
755        self.Apply()
756        self.SetThemeType()
757
758    def GetColour(self):
759        target=self.highlightTarget.get()
760        prevColour=self.frameColourSet.cget('bg')
761        rgbTuplet, colourString = tkColorChooser.askcolor(parent=self,
762            title='Pick new colour for : '+target,initialcolor=prevColour)
763        if colourString and (colourString!=prevColour):
764            #user didn't cancel, and they chose a new colour
765            if self.themeIsBuiltin.get(): #current theme is a built-in
766                message=('Your changes will be saved as a new Custom Theme. '+
767                        'Enter a name for your new Custom Theme below.')
768                newTheme=self.GetNewThemeName(message)
769                if not newTheme: #user cancelled custom theme creation
770                    return
771                else: #create new custom theme based on previously active theme
772                    self.CreateNewTheme(newTheme)
773                    self.colour.set(colourString)
774            else: #current theme is user defined
775                self.colour.set(colourString)
776
777    def OnNewColourSet(self):
778        newColour=self.colour.get()
779        self.frameColourSet.config(bg=newColour)#set sample
780        if self.fgHilite.get(): plane='foreground'
781        else: plane='background'
782        sampleElement=self.themeElements[self.highlightTarget.get()][0]
783        self.textHighlightSample.tag_config(sampleElement, **{plane:newColour})
784        theme=self.customTheme.get()
785        themeElement=sampleElement+'-'+plane
786        self.AddChangedItem('highlight',theme,themeElement,newColour)
787
788    def GetNewThemeName(self,message):
789        usedNames=(idleConf.GetSectionList('user','highlight')+
790                idleConf.GetSectionList('default','highlight'))
791        newTheme=GetCfgSectionNameDialog(self,'New Custom Theme',
792                message,usedNames).result
793        return newTheme
794
795    def SaveAsNewTheme(self):
796        newThemeName=self.GetNewThemeName('New Theme Name:')
797        if newThemeName:
798            self.CreateNewTheme(newThemeName)
799
800    def CreateNewTheme(self,newThemeName):
801        #creates new custom theme based on the previously active theme,
802        #and makes the new theme active
803        if self.themeIsBuiltin.get():
804            themeType='default'
805            themeName=self.builtinTheme.get()
806        else:
807            themeType='user'
808            themeName=self.customTheme.get()
809        newTheme=idleConf.GetThemeDict(themeType,themeName)
810        #apply any of the old theme's unsaved changes to the new theme
811        if themeName in self.changedItems['highlight'].keys():
812            themeChanges=self.changedItems['highlight'][themeName]
813            for element in themeChanges.keys():
814                newTheme[element]=themeChanges[element]
815        #save the new theme
816        self.SaveNewTheme(newThemeName,newTheme)
817        #change gui over to the new theme
818        customThemeList=idleConf.GetSectionList('user','highlight')
819        customThemeList.sort()
820        self.optMenuThemeCustom.SetMenu(customThemeList,newThemeName)
821        self.themeIsBuiltin.set(0)
822        self.SetThemeType()
823
824    def OnListFontButtonRelease(self,event):
825        font = self.listFontName.get(ANCHOR)
826        self.fontName.set(font.lower())
827        self.SetFontSample()
828
829    def SetFontSample(self,event=None):
830        fontName=self.fontName.get()
831        if self.fontBold.get():
832            fontWeight=tkFont.BOLD
833        else:
834            fontWeight=tkFont.NORMAL
835        newFont = (fontName, self.fontSize.get(), fontWeight)
836        self.labelFontSample.config(font=newFont)
837        self.textHighlightSample.configure(font=newFont)
838
839    def SetHighlightTarget(self):
840        if self.highlightTarget.get()=='Cursor': #bg not possible
841            self.radioFg.config(state=DISABLED)
842            self.radioBg.config(state=DISABLED)
843            self.fgHilite.set(1)
844        else: #both fg and bg can be set
845            self.radioFg.config(state=NORMAL)
846            self.radioBg.config(state=NORMAL)
847            self.fgHilite.set(1)
848        self.SetColourSample()
849
850    def SetColourSampleBinding(self,*args):
851        self.SetColourSample()
852
853    def SetColourSample(self):
854        #set the colour smaple area
855        tag=self.themeElements[self.highlightTarget.get()][0]
856        if self.fgHilite.get(): plane='foreground'
857        else: plane='background'
858        colour=self.textHighlightSample.tag_cget(tag,plane)
859        self.frameColourSet.config(bg=colour)
860
861    def PaintThemeSample(self):
862        if self.themeIsBuiltin.get(): #a default theme
863            theme=self.builtinTheme.get()
864        else: #a user theme
865            theme=self.customTheme.get()
866        for elementTitle in self.themeElements.keys():
867            element=self.themeElements[elementTitle][0]
868            colours=idleConf.GetHighlight(theme,element)
869            if element=='cursor': #cursor sample needs special painting
870                colours['background']=idleConf.GetHighlight(theme,
871                        'normal', fgBg='bg')
872            #handle any unsaved changes to this theme
873            if theme in self.changedItems['highlight'].keys():
874                themeDict=self.changedItems['highlight'][theme]
875                if element+'-foreground' in themeDict:
876                    colours['foreground']=themeDict[element+'-foreground']
877                if element+'-background' in themeDict:
878                    colours['background']=themeDict[element+'-background']
879            self.textHighlightSample.tag_config(element, **colours)
880        self.SetColourSample()
881
882    def HelpSourceSelected(self,event):
883        self.SetHelpListButtonStates()
884
885    def SetHelpListButtonStates(self):
886        if self.listHelp.size()<1: #no entries in list
887            self.buttonHelpListEdit.config(state=DISABLED)
888            self.buttonHelpListRemove.config(state=DISABLED)
889        else: #there are some entries
890            if self.listHelp.curselection(): #there currently is a selection
891                self.buttonHelpListEdit.config(state=NORMAL)
892                self.buttonHelpListRemove.config(state=NORMAL)
893            else:  #there currently is not a selection
894                self.buttonHelpListEdit.config(state=DISABLED)
895                self.buttonHelpListRemove.config(state=DISABLED)
896
897    def HelpListItemAdd(self):
898        helpSource=GetHelpSourceDialog(self,'New Help Source').result
899        if helpSource:
900            self.userHelpList.append( (helpSource[0],helpSource[1]) )
901            self.listHelp.insert(END,helpSource[0])
902            self.UpdateUserHelpChangedItems()
903        self.SetHelpListButtonStates()
904
905    def HelpListItemEdit(self):
906        itemIndex=self.listHelp.index(ANCHOR)
907        helpSource=self.userHelpList[itemIndex]
908        newHelpSource=GetHelpSourceDialog(self,'Edit Help Source',
909                menuItem=helpSource[0],filePath=helpSource[1]).result
910        if (not newHelpSource) or (newHelpSource==helpSource):
911            return #no changes
912        self.userHelpList[itemIndex]=newHelpSource
913        self.listHelp.delete(itemIndex)
914        self.listHelp.insert(itemIndex,newHelpSource[0])
915        self.UpdateUserHelpChangedItems()
916        self.SetHelpListButtonStates()
917
918    def HelpListItemRemove(self):
919        itemIndex=self.listHelp.index(ANCHOR)
920        del(self.userHelpList[itemIndex])
921        self.listHelp.delete(itemIndex)
922        self.UpdateUserHelpChangedItems()
923        self.SetHelpListButtonStates()
924
925    def UpdateUserHelpChangedItems(self):
926        "Clear and rebuild the HelpFiles section in self.changedItems"
927        self.changedItems['main']['HelpFiles'] = {}
928        for num in range(1,len(self.userHelpList)+1):
929            self.AddChangedItem('main','HelpFiles',str(num),
930                    string.join(self.userHelpList[num-1][:2],';'))
931
932    def LoadFontCfg(self):
933        ##base editor font selection list
934        fonts=list(tkFont.families(self))
935        fonts.sort()
936        for font in fonts:
937            self.listFontName.insert(END,font)
938        configuredFont=idleConf.GetOption('main','EditorWindow','font',
939                default='courier')
940        lc_configuredFont = configuredFont.lower()
941        self.fontName.set(lc_configuredFont)
942        lc_fonts = [s.lower() for s in fonts]
943        if lc_configuredFont in lc_fonts:
944            currentFontIndex = lc_fonts.index(lc_configuredFont)
945            self.listFontName.see(currentFontIndex)
946            self.listFontName.select_set(currentFontIndex)
947            self.listFontName.select_anchor(currentFontIndex)
948        ##font size dropdown
949        fontSize=idleConf.GetOption('main','EditorWindow','font-size',
950                type='int', default='10')
951        self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14',
952                '16','18','20','22'),fontSize )
953        ##fontWeight
954        self.fontBold.set(idleConf.GetOption('main','EditorWindow',
955                'font-bold',default=0,type='bool'))
956        ##font sample
957        self.SetFontSample()
958
959    def LoadTabCfg(self):
960        ##indent sizes
961        spaceNum=idleConf.GetOption('main','Indent','num-spaces',
962                default=4,type='int')
963        self.spaceNum.set(spaceNum)
964
965    def LoadThemeCfg(self):
966        ##current theme type radiobutton
967        self.themeIsBuiltin.set(idleConf.GetOption('main','Theme','default',
968            type='bool',default=1))
969        ##currently set theme
970        currentOption=idleConf.CurrentTheme()
971        ##load available theme option menus
972        if self.themeIsBuiltin.get(): #default theme selected
973            itemList=idleConf.GetSectionList('default','highlight')
974            itemList.sort()
975            self.optMenuThemeBuiltin.SetMenu(itemList,currentOption)
976            itemList=idleConf.GetSectionList('user','highlight')
977            itemList.sort()
978            if not itemList:
979                self.radioThemeCustom.config(state=DISABLED)
980                self.customTheme.set('- no custom themes -')
981            else:
982                self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
983        else: #user theme selected
984            itemList=idleConf.GetSectionList('user','highlight')
985            itemList.sort()
986            self.optMenuThemeCustom.SetMenu(itemList,currentOption)
987            itemList=idleConf.GetSectionList('default','highlight')
988            itemList.sort()
989            self.optMenuThemeBuiltin.SetMenu(itemList,itemList[0])
990        self.SetThemeType()
991        ##load theme element option menu
992        themeNames=self.themeElements.keys()
993        themeNames.sort(key=lambda x: self.themeElements[x][1])
994        self.optMenuHighlightTarget.SetMenu(themeNames,themeNames[0])
995        self.PaintThemeSample()
996        self.SetHighlightTarget()
997
998    def LoadKeyCfg(self):
999        ##current keys type radiobutton
1000        self.keysAreBuiltin.set(idleConf.GetOption('main','Keys','default',
1001            type='bool',default=1))
1002        ##currently set keys
1003        currentOption=idleConf.CurrentKeys()
1004        ##load available keyset option menus
1005        if self.keysAreBuiltin.get(): #default theme selected
1006            itemList=idleConf.GetSectionList('default','keys')
1007            itemList.sort()
1008            self.optMenuKeysBuiltin.SetMenu(itemList,currentOption)
1009            itemList=idleConf.GetSectionList('user','keys')
1010            itemList.sort()
1011            if not itemList:
1012                self.radioKeysCustom.config(state=DISABLED)
1013                self.customKeys.set('- no custom keys -')
1014            else:
1015                self.optMenuKeysCustom.SetMenu(itemList,itemList[0])
1016        else: #user key set selected
1017            itemList=idleConf.GetSectionList('user','keys')
1018            itemList.sort()
1019            self.optMenuKeysCustom.SetMenu(itemList,currentOption)
1020            itemList=idleConf.GetSectionList('default','keys')
1021            itemList.sort()
1022            self.optMenuKeysBuiltin.SetMenu(itemList,itemList[0])
1023        self.SetKeysType()
1024        ##load keyset element list
1025        keySetName=idleConf.CurrentKeys()
1026        self.LoadKeysList(keySetName)
1027
1028    def LoadGeneralCfg(self):
1029        #startup state
1030        self.startupEdit.set(idleConf.GetOption('main','General',
1031                'editor-on-startup',default=1,type='bool'))
1032        #autosave state
1033        self.autoSave.set(idleConf.GetOption('main', 'General', 'autosave',
1034                                             default=0, type='bool'))
1035        #initial window size
1036        self.winWidth.set(idleConf.GetOption('main','EditorWindow','width',
1037                                             type='int'))
1038        self.winHeight.set(idleConf.GetOption('main','EditorWindow','height',
1039                                              type='int'))
1040        #initial paragraph reformat size
1041        self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph',
1042                                              type='int'))
1043        # default source encoding
1044        self.encoding.set(idleConf.GetOption('main', 'EditorWindow',
1045                                             'encoding', default='none'))
1046        # additional help sources
1047        self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
1048        for helpItem in self.userHelpList:
1049            self.listHelp.insert(END,helpItem[0])
1050        self.SetHelpListButtonStates()
1051
1052    def LoadConfigs(self):
1053        """
1054        load configuration from default and user config files and populate
1055        the widgets on the config dialog pages.
1056        """
1057        ### fonts / tabs page
1058        self.LoadFontCfg()
1059        self.LoadTabCfg()
1060        ### highlighting page
1061        self.LoadThemeCfg()
1062        ### keys page
1063        self.LoadKeyCfg()
1064        ### general page
1065        self.LoadGeneralCfg()
1066
1067    def SaveNewKeySet(self,keySetName,keySet):
1068        """
1069        save a newly created core key set.
1070        keySetName - string, the name of the new key set
1071        keySet - dictionary containing the new key set
1072        """
1073        if not idleConf.userCfg['keys'].has_section(keySetName):
1074            idleConf.userCfg['keys'].add_section(keySetName)
1075        for event in keySet.keys():
1076            value=keySet[event]
1077            idleConf.userCfg['keys'].SetOption(keySetName,event,value)
1078
1079    def SaveNewTheme(self,themeName,theme):
1080        """
1081        save a newly created theme.
1082        themeName - string, the name of the new theme
1083        theme - dictionary containing the new theme
1084        """
1085        if not idleConf.userCfg['highlight'].has_section(themeName):
1086            idleConf.userCfg['highlight'].add_section(themeName)
1087        for element in theme.keys():
1088            value=theme[element]
1089            idleConf.userCfg['highlight'].SetOption(themeName,element,value)
1090
1091    def SetUserValue(self,configType,section,item,value):
1092        if idleConf.defaultCfg[configType].has_option(section,item):
1093            if idleConf.defaultCfg[configType].Get(section,item)==value:
1094                #the setting equals a default setting, remove it from user cfg
1095                return idleConf.userCfg[configType].RemoveOption(section,item)
1096        #if we got here set the option
1097        return idleConf.userCfg[configType].SetOption(section,item,value)
1098
1099    def SaveAllChangedConfigs(self):
1100        "Save configuration changes to the user config file."
1101        idleConf.userCfg['main'].Save()
1102        for configType in self.changedItems.keys():
1103            cfgTypeHasChanges = False
1104            for section in self.changedItems[configType].keys():
1105                if section == 'HelpFiles':
1106                    #this section gets completely replaced
1107                    idleConf.userCfg['main'].remove_section('HelpFiles')
1108                    cfgTypeHasChanges = True
1109                for item in self.changedItems[configType][section].keys():
1110                    value = self.changedItems[configType][section][item]
1111                    if self.SetUserValue(configType,section,item,value):
1112                        cfgTypeHasChanges = True
1113            if cfgTypeHasChanges:
1114                idleConf.userCfg[configType].Save()
1115        for configType in ['keys', 'highlight']:
1116            # save these even if unchanged!
1117            idleConf.userCfg[configType].Save()
1118        self.ResetChangedItems() #clear the changed items dict
1119
1120    def DeactivateCurrentConfig(self):
1121        #Before a config is saved, some cleanup of current
1122        #config must be done - remove the previous keybindings
1123        winInstances=self.parent.instance_dict.keys()
1124        for instance in winInstances:
1125            instance.RemoveKeybindings()
1126
1127    def ActivateConfigChanges(self):
1128        "Dynamically apply configuration changes"
1129        winInstances=self.parent.instance_dict.keys()
1130        for instance in winInstances:
1131            instance.ResetColorizer()
1132            instance.ResetFont()
1133            instance.set_notabs_indentwidth()
1134            instance.ApplyKeybindings()
1135            instance.reset_help_menu_entries()
1136
1137    def Cancel(self):
1138        self.destroy()
1139
1140    def Ok(self):
1141        self.Apply()
1142        self.destroy()
1143
1144    def Apply(self):
1145        self.DeactivateCurrentConfig()
1146        self.SaveAllChangedConfigs()
1147        self.ActivateConfigChanges()
1148
1149    def Help(self):
1150        pass
1151
1152if __name__ == '__main__':
1153    #test the dialog
1154    root=Tk()
1155    Button(root,text='Dialog',
1156            command=lambda:ConfigDialog(root,'Settings')).pack()
1157    root.instance_dict={}
1158    root.mainloop()
1159