Home | History | Annotate | Download | only in idlelib
      1 """IDLE Configuration Dialog: support user customization of IDLE by GUI
      2 
      3 Customize font faces, sizes, and colorization attributes.  Set indentation
      4 defaults.  Customize keybindings.  Colorization and keybindings can be
      5 saved as user defined sets.  Select startup options including shell/editor
      6 and default window size.  Define additional help sources.
      7 
      8 Note that tab width in IDLE is currently fixed at eight due to Tk issues.
      9 Refer to comments in EditorWindow autoindent code for details.
     10 
     11 """
     12 from Tkinter import *
     13 import tkMessageBox, tkColorChooser, tkFont
     14 import string
     15 
     16 from idlelib.configHandler import idleConf
     17 from idlelib.dynOptionMenuWidget import DynOptionMenu
     18 from idlelib.tabbedpages import TabbedPageSet
     19 from idlelib.keybindingDialog import GetKeysDialog
     20 from idlelib.configSectionNameDialog import GetCfgSectionNameDialog
     21 from idlelib.configHelpSourceEdit import GetHelpSourceDialog
     22 from idlelib import macosxSupport
     23 
     24 class 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 
   1152 if __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