blob: 196d9527116d6a51dd71d841943a16bacba40b7a [file] [log] [blame]
Steven M. Gava2a63a072001-10-26 06:50:54 +00001"""
Steven M. Gavaad4f5322002-01-03 12:05:17 +00002Provides access to stored idle configuration information.
Steven M. Gavaad4f5322002-01-03 12:05:17 +00003"""
Steven M. Gavac5976402002-01-04 03:06:08 +00004# Throughout this module there is an emphasis on returning useable defaults
5# when a problem occurs in returning a requested configuration value back to
6# idle. This is to allow idle to continue to function in spite of errors in
7# the retrieval of config information. When a default is returned instead of
8# a requested config value, a message is printed to stderr to aid in
9# configuration problem notification and resolution.
10
Steven M. Gavac11ccf32001-09-24 09:43:17 +000011import os
12import sys
13from ConfigParser import ConfigParser, NoOptionError, NoSectionError
14
15class IdleConfParser(ConfigParser):
16 """
17 A ConfigParser specialised for idle configuration file handling
18 """
19 def __init__(self, cfgFile, cfgDefaults=None):
20 """
21 cfgFile - string, fully specified configuration file name
22 """
23 self.file=cfgFile
24 ConfigParser.__init__(self,defaults=cfgDefaults)
25
Steven M. Gava5f28e8f2002-01-21 06:38:21 +000026 def Get(self, section, option, type=None):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000027 """
28 Get an option value for given section/option or return default.
29 If type is specified, return as type.
30 """
Steven M. Gava41a85322001-10-29 08:05:34 +000031 if type=='bool':
32 getVal=self.getboolean
33 elif type=='int':
34 getVal=self.getint
35 else:
36 getVal=self.get
Steven M. Gavac11ccf32001-09-24 09:43:17 +000037 if self.has_option(section,option):
Steven M. Gava429a86a2001-10-23 10:42:12 +000038 #return getVal(section, option, raw, vars)
39 return getVal(section, option)
Steven M. Gavac11ccf32001-09-24 09:43:17 +000040
Steven M. Gavac11ccf32001-09-24 09:43:17 +000041 def GetOptionList(self,section):
42 """
43 Get an option list for given section
44 """
45 if self.has_section:
46 return self.options(section)
47 else: #return a default value
48 return []
49
Steven M. Gavac11ccf32001-09-24 09:43:17 +000050 def Load(self):
51 """
52 Load the configuration file from disk
53 """
54 self.read(self.file)
55
56class IdleUserConfParser(IdleConfParser):
57 """
58 IdleConfigParser specialised for user configuration handling
59 """
60 def Save(self):
61 """
62 write loaded user configuration file back to disk
63 """
64 # this is a user config, it can be written to disk
65 self.write()
66
67class IdleConf:
68 """
69 holds config parsers for all idle config files:
70 default config files
71 (idle install dir)/config-main.def
72 (idle install dir)/config-extensions.def
73 (idle install dir)/config-highlight.def
74 (idle install dir)/config-keys.def
75 user config files
Steven M. Gavaad4f5322002-01-03 12:05:17 +000076 (user home dir)/.idlerc/config-main.cfg
77 (user home dir)/.idlerc/config-extensions.cfg
78 (user home dir)/.idlerc/config-highlight.cfg
79 (user home dir)/.idlerc/config-keys.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +000080 """
81 def __init__(self):
82 self.defaultCfg={}
83 self.userCfg={}
84 self.cfg={}
85 self.CreateConfigHandlers()
86 self.LoadCfgFiles()
87 #self.LoadCfg()
88
89 def CreateConfigHandlers(self):
90 """
Steven M. Gavaad4f5322002-01-03 12:05:17 +000091 set up a dictionary of config parsers for default and user
Steven M. Gavac11ccf32001-09-24 09:43:17 +000092 configurations respectively
93 """
94 #build idle install path
95 if __name__ != '__main__': # we were imported
96 idledir=os.path.dirname(__file__)
97 else: # we were exec'ed (for testing only)
98 idledir=os.path.abspath(sys.path[0])
99 #print idledir
100 try: #build user home path
101 userdir = os.environ['HOME'] #real home directory
102 except KeyError:
103 userdir = os.getcwd() #hack for os'es without real homedirs
104 userdir=os.path.join(userdir,'.idlerc')
105 #print userdir
106 if not os.path.exists(userdir):
107 os.mkdir(userdir)
108 configTypes=('main','extensions','highlight','keys')
109 defCfgFiles={}
110 usrCfgFiles={}
111 for cfgType in configTypes: #build config file names
112 defCfgFiles[cfgType]=os.path.join(idledir,'config-'+cfgType+'.def')
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000113 usrCfgFiles[cfgType]=os.path.join(userdir,'config-'+cfgType+'.cfg')
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000114 for cfgType in configTypes: #create config parsers
115 self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
116 self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])
117
Steven M. Gava2a63a072001-10-26 06:50:54 +0000118 def GetOption(self, configType, section, option, default=None, type=None):
Steven M. Gava429a86a2001-10-23 10:42:12 +0000119 """
120 Get an option value for given config type and given general
121 configuration section/option or return a default. If type is specified,
122 return as type. Firstly the user configuration is checked, with a
123 fallback to the default configuration, and a final 'catch all'
124 fallback to a useable passed-in default if the option isn't present in
125 either the user or the default configuration.
126 configType must be one of ('main','extensions','highlight','keys')
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000127 If a default is returned a warning is printed to stderr.
Steven M. Gava429a86a2001-10-23 10:42:12 +0000128 """
129 if self.userCfg[configType].has_option(section,option):
130 return self.userCfg[configType].Get(section, option, type=type)
131 elif self.defaultCfg[configType].has_option(section,option):
132 return self.defaultCfg[configType].Get(section, option, type=type)
133 else:
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000134 warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
135 ' problem retrieving configration option '+`option`+'\n'+
136 ' from section '+`section`+'.\n'+
137 ' returning default value: '+`default`+'\n')
138 sys.stderr.write(warning)
Steven M. Gava429a86a2001-10-23 10:42:12 +0000139 return default
140
Steven M. Gava2a63a072001-10-26 06:50:54 +0000141 def GetSectionList(self, configSet, configType):
142 """
143 Get a list of sections from either the user or default config for
144 the given config type.
145 configSet must be either 'user' or 'default'
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000146 configType must be one of ('main','extensions','highlight','keys')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000147 """
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000148 if not (configType in ('main','extensions','highlight','keys')):
Steven M. Gava2a63a072001-10-26 06:50:54 +0000149 raise 'Invalid configType specified'
150 if configSet == 'user':
151 cfgParser=self.userCfg[configType]
152 elif configSet == 'default':
153 cfgParser=self.defaultCfg[configType]
154 else:
155 raise 'Invalid configSet specified'
Steven M. Gava2a63a072001-10-26 06:50:54 +0000156 return cfgParser.sections()
157
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000158 def GetHighlight(self, theme, element, fgBg=None):
159 """
160 return individual highlighting theme elements.
161 fgBg - string ('fg'or'bg') or None, if None return a dictionary
162 containing fg and bg colours (appropriate for passing to Tkinter in,
163 e.g., a tag_config call), otherwise fg or bg colour only as specified.
164 """
Steven M. Gava99300612001-11-04 07:03:08 +0000165 #get some fallback defaults
166 defaultFg=self.GetOption('highlight', theme, 'normal' + "-foreground",
167 default='#000000')
168 defaultBg=self.GetOption('highlight', theme, 'normal' + "-background",
169 default='#ffffff')
170 #try for requested element colours
Steven M. Gavae16d94b2001-11-03 05:07:28 +0000171 fore = self.GetOption('highlight', theme, element + "-foreground")
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000172 back = None
173 if element == 'cursor': #there is no config value for cursor bg
174 back = None
175 else:
176 back = self.GetOption('highlight', theme, element + "-background")
Steven M. Gava99300612001-11-04 07:03:08 +0000177 #fall back if required
178 if not fore: fore=defaultFg
179 if not back: back=defaultBg
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000180 highlight={"foreground": fore,"background": back}
181 if not fgBg: #return dict of both colours
182 return highlight
183 else: #return specified colour only
184 if fgBg == 'fg':
185 return highlight["foreground"]
186 if fgBg == 'bg':
187 return highlight["background"]
188 else:
189 raise 'Invalid fgBg specified'
190
Steven M. Gavae16d94b2001-11-03 05:07:28 +0000191
Steven M. Gava2a63a072001-10-26 06:50:54 +0000192 def GetTheme(self, name=None):
193 """
194 Gets the requested theme or returns a final fallback theme in case
195 one can't be obtained from either the user or default config files.
196 """
197 pass
198
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000199 def CurrentTheme(self):
200 """
201 Returns the name of the currently active theme
202 """
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000203 return self.GetOption('main','Theme','name',default='')
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000204
205
206 def CurrentKeys(self):
207 """
208 Returns the name of the currently active theme
209 """
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000210 return self.GetOption('main','Keys','name',default='')
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000211
212 def GetExtensions(self, activeOnly=1):
213 """
214 Gets a list of all idle extensions declared in the config files.
215 activeOnly - boolean, if true only return active (enabled) extensions
216 """
Steven M. Gavac628a062002-01-19 10:33:21 +0000217 extns=self.RemoveKeyBindNames(
218 self.GetSectionList('default','extensions'))
219 userExtns=self.RemoveKeyBindNames(
220 self.GetSectionList('user','extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000221 for extn in userExtns:
222 if extn not in extns: #user has added own extension
223 extns.append(extn)
224 if activeOnly:
225 activeExtns=[]
226 for extn in extns:
Steven M. Gava5f28e8f2002-01-21 06:38:21 +0000227 if self.GetOption('extensions',extn,'enable',default=1,
228 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000229 #the extension is enabled
230 activeExtns.append(extn)
231 return activeExtns
232 else:
233 return extns
234
Steven M. Gavac628a062002-01-19 10:33:21 +0000235 def RemoveKeyBindNames(self,extnNameList):
236 #get rid of keybinding section names
237 names=extnNameList
238 kbNameIndicies=[]
239 for name in names:
240 if name.endswith('_bindings') or name.endswith('_cfgBindings'):
241 kbNameIndicies.append(names.index(name))
242 kbNameIndicies.sort()
243 kbNameIndicies.reverse()
244 for index in kbNameIndicies: #delete each keybinding section name
245 del(names[index])
246 return names
247
248 def GetExtensionKeys(self,extensionName):
249 """
250 returns a dictionary of the configurable keybindings for a particular
251 extension,as they exist in the dictionary returned by GetCurrentKeySet;
252 that is, where previously re-used bindings are disabled.
253 """
254 keysName=extensionName+'_cfgBindings'
255 activeKeys=self.GetCurrentKeySet()
256 extKeys={}
257 if self.defaultCfg['extensions'].has_section(keysName):
258 eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
259 for eventName in eventNames:
260 event='<<'+eventName+'>>'
261 binding=activeKeys[event]
262 extKeys[event]=binding
263 return extKeys
264
265 def __GetRawExtensionKeys(self,extensionName):
266 """
267 returns a dictionary of the configurable keybindings for a particular
268 extension, as defined in the configuration files, or an empty dictionary
269 if no bindings are found
270 """
271 keysName=extensionName+'_cfgBindings'
272 extKeys={}
273 if self.defaultCfg['extensions'].has_section(keysName):
274 eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
275 for eventName in eventNames:
276 binding=self.GetOption('extensions',keysName,
277 eventName,default='').split()
278 event='<<'+eventName+'>>'
279 extKeys[event]=binding
280 return extKeys
281
282 def GetExtensionBindings(self,extensionName):
283 """
284 Returns a dictionary of all the event bindings for a particular
285 extension. The configurable keybindings are returned as they exist in
286 the dictionary returned by GetCurrentKeySet; that is, where re-used
287 keybindings are disabled.
288 """
289 bindsName=extensionName+'_bindings'
290 extBinds=self.GetExtensionKeys(extensionName)
291 #add the non-configurable bindings
292 if self.defaultCfg['extensions'].has_section(bindsName):
293 eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
294 for eventName in eventNames:
295 binding=self.GetOption('extensions',bindsName,
296 eventName,default='').split()
297 event='<<'+eventName+'>>'
298 extBinds[event]=binding
299
300 return extBinds
301
302
303
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000304 def GetKeyBinding(self, keySetName, eventStr):
305 """
306 returns the keybinding for a specific event.
307 keySetName - string, name of key binding set
308 eventStr - string, the virtual event we want the binding for,
309 represented as a string, eg. '<<event>>'
310 """
311 eventName=eventStr[2:-2] #trim off the angle brackets
312 binding=self.GetOption('keys',keySetName,eventName,default='').split()
313 return binding
314
Steven M. Gavac628a062002-01-19 10:33:21 +0000315 def GetCurrentKeySet(self):
Steven M. Gava2a63a072001-10-26 06:50:54 +0000316 """
Steven M. Gavac628a062002-01-19 10:33:21 +0000317 Returns a dictionary of: all current core keybindings, plus the
318 keybindings for all currently active extensions. If a binding defined
319 in an extension is already in use, that binding is disabled.
320 """
321 currentKeySet=self.GetCoreKeys(keySetName=self.CurrentKeys())
322 activeExtns=self.GetExtensions(activeOnly=1)
323 for extn in activeExtns:
324 extKeys=self.__GetRawExtensionKeys(extn)
325 if extKeys: #the extension defines keybindings
326 for event in extKeys.keys():
327 if extKeys[event] in currentKeySet.values():
328 #the binding is already in use
329 extKeys[event]='' #disable this binding
330 currentKeySet[event]=extKeys[event] #add binding
331 return currentKeySet
332
333 def GetCoreKeys(self, keySetName=None):
334 """
335 returns the requested set of core keybindings, with fallbacks if
336 required.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000337 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000338 #keybindings loaded from the config file(s) are loaded _over_ these
Steven M. Gavac628a062002-01-19 10:33:21 +0000339 #defaults, so if there is a problem getting any core binding there will
Steven M. Gava17d01542001-12-03 00:37:28 +0000340 #be an 'ultimate last resort fallback' to the CUA-ish bindings
341 #defined here.
342 keyBindings={
343 '<<Copy>>': ['<Control-c>', '<Control-C>'],
344 '<<Cut>>': ['<Control-x>', '<Control-X>'],
345 '<<Paste>>': ['<Control-v>', '<Control-V>'],
346 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
347 '<<center-insert>>': ['<Control-l>'],
348 '<<close-all-windows>>': ['<Control-q>'],
349 '<<close-window>>': ['<Alt-F4>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000350 '<<end-of-file>>': ['<Control-d>'],
351 '<<python-docs>>': ['<F1>'],
352 '<<python-context-help>>': ['<Shift-F1>'],
353 '<<history-next>>': ['<Alt-n>'],
354 '<<history-previous>>': ['<Alt-p>'],
355 '<<interrupt-execution>>': ['<Control-c>'],
356 '<<open-class-browser>>': ['<Alt-c>'],
357 '<<open-module>>': ['<Alt-m>'],
358 '<<open-new-window>>': ['<Control-n>'],
359 '<<open-window-from-file>>': ['<Control-o>'],
360 '<<plain-newline-and-indent>>': ['<Control-j>'],
361 '<<redo>>': ['<Control-y>'],
362 '<<remove-selection>>': ['<Escape>'],
363 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
364 '<<save-window-as-file>>': ['<Alt-s>'],
365 '<<save-window>>': ['<Control-s>'],
366 '<<select-all>>': ['<Alt-a>'],
367 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000368 '<<undo>>': ['<Control-z>'],
369 '<<find-again>>': ['<Control-g>', '<F3>'],
370 '<<find-in-files>>': ['<Alt-F3>'],
371 '<<find-selection>>': ['<Control-F3>'],
372 '<<find>>': ['<Control-f>'],
373 '<<replace>>': ['<Control-h>'],
374 '<<goto-line>>': ['<Alt-g>'] }
375
Steven M. Gava17d01542001-12-03 00:37:28 +0000376 if keySetName:
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000377 for event in keyBindings.keys():
378 binding=self.GetKeyBinding(keySetName,event)
379 if binding: #otherwise will keep default
380 keyBindings[event]=binding
Steven M. Gava17d01542001-12-03 00:37:28 +0000381
382 return keyBindings
383
Steven M. Gava2a63a072001-10-26 06:50:54 +0000384
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000385 def LoadCfgFiles(self):
386 """
387 load all configuration files.
388 """
389 for key in self.defaultCfg.keys():
390 self.defaultCfg[key].Load()
391 self.userCfg[key].Load() #same keys
392
393 def SaveUserCfgFiles(self):
394 """
395 write all loaded user configuration files back to disk
396 """
397 for key in self.userCfg.keys():
398 self.userCfg[key].Save()
399
400idleConf=IdleConf()
401
402### module test
403if __name__ == '__main__':
404 def dumpCfg(cfg):
405 print '\n',cfg,'\n'
406 for key in cfg.keys():
407 sections=cfg[key].sections()
408 print key
409 print sections
410 for section in sections:
411 options=cfg[key].options(section)
412 print section
413 print options
414 for option in options:
415 print option, '=', cfg[key].Get(section,option)
416 dumpCfg(idleConf.defaultCfg)
417 dumpCfg(idleConf.userCfg)
418 print idleConf.userCfg['main'].Get('Theme','name')
419 #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')