blob: 34be897d1e5973ccfe26895ba9120784b07c325f [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. Gavaad4f5322002-01-03 12:05:17 +000026 def Get(self, section, option, type=None): #,default=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. Gava429a86af2001-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. Gava429a86af2001-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. Gava429a86af2001-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. Gava429a86af2001-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'
146 configType must be one of ('extensions','highlight','keys')
147 """
148 if not (configType in ('extensions','highlight','keys')):
149 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:
227 if self.GetOption('extensions',extn,'enable',default=1,type='bool'):
228 #the extension is enabled
229 activeExtns.append(extn)
230 return activeExtns
231 else:
232 return extns
233
Steven M. Gavac628a062002-01-19 10:33:21 +0000234 def RemoveKeyBindNames(self,extnNameList):
235 #get rid of keybinding section names
236 names=extnNameList
237 kbNameIndicies=[]
238 for name in names:
239 if name.endswith('_bindings') or name.endswith('_cfgBindings'):
240 kbNameIndicies.append(names.index(name))
241 kbNameIndicies.sort()
242 kbNameIndicies.reverse()
243 for index in kbNameIndicies: #delete each keybinding section name
244 del(names[index])
245 return names
246
247 def GetExtensionKeys(self,extensionName):
248 """
249 returns a dictionary of the configurable keybindings for a particular
250 extension,as they exist in the dictionary returned by GetCurrentKeySet;
251 that is, where previously re-used bindings are disabled.
252 """
253 keysName=extensionName+'_cfgBindings'
254 activeKeys=self.GetCurrentKeySet()
255 extKeys={}
256 if self.defaultCfg['extensions'].has_section(keysName):
257 eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
258 for eventName in eventNames:
259 event='<<'+eventName+'>>'
260 binding=activeKeys[event]
261 extKeys[event]=binding
262 return extKeys
263
264 def __GetRawExtensionKeys(self,extensionName):
265 """
266 returns a dictionary of the configurable keybindings for a particular
267 extension, as defined in the configuration files, or an empty dictionary
268 if no bindings are found
269 """
270 keysName=extensionName+'_cfgBindings'
271 extKeys={}
272 if self.defaultCfg['extensions'].has_section(keysName):
273 eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
274 for eventName in eventNames:
275 binding=self.GetOption('extensions',keysName,
276 eventName,default='').split()
277 event='<<'+eventName+'>>'
278 extKeys[event]=binding
279 return extKeys
280
281 def GetExtensionBindings(self,extensionName):
282 """
283 Returns a dictionary of all the event bindings for a particular
284 extension. The configurable keybindings are returned as they exist in
285 the dictionary returned by GetCurrentKeySet; that is, where re-used
286 keybindings are disabled.
287 """
288 bindsName=extensionName+'_bindings'
289 extBinds=self.GetExtensionKeys(extensionName)
290 #add the non-configurable bindings
291 if self.defaultCfg['extensions'].has_section(bindsName):
292 eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
293 for eventName in eventNames:
294 binding=self.GetOption('extensions',bindsName,
295 eventName,default='').split()
296 event='<<'+eventName+'>>'
297 extBinds[event]=binding
298
299 return extBinds
300
301
302
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000303 def GetKeyBinding(self, keySetName, eventStr):
304 """
305 returns the keybinding for a specific event.
306 keySetName - string, name of key binding set
307 eventStr - string, the virtual event we want the binding for,
308 represented as a string, eg. '<<event>>'
309 """
310 eventName=eventStr[2:-2] #trim off the angle brackets
311 binding=self.GetOption('keys',keySetName,eventName,default='').split()
312 return binding
313
Steven M. Gavac628a062002-01-19 10:33:21 +0000314 def GetCurrentKeySet(self):
Steven M. Gava2a63a072001-10-26 06:50:54 +0000315 """
Steven M. Gavac628a062002-01-19 10:33:21 +0000316 Returns a dictionary of: all current core keybindings, plus the
317 keybindings for all currently active extensions. If a binding defined
318 in an extension is already in use, that binding is disabled.
319 """
320 currentKeySet=self.GetCoreKeys(keySetName=self.CurrentKeys())
321 activeExtns=self.GetExtensions(activeOnly=1)
322 for extn in activeExtns:
323 extKeys=self.__GetRawExtensionKeys(extn)
324 if extKeys: #the extension defines keybindings
325 for event in extKeys.keys():
326 if extKeys[event] in currentKeySet.values():
327 #the binding is already in use
328 extKeys[event]='' #disable this binding
329 currentKeySet[event]=extKeys[event] #add binding
330 return currentKeySet
331
332 def GetCoreKeys(self, keySetName=None):
333 """
334 returns the requested set of core keybindings, with fallbacks if
335 required.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000336 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000337 #keybindings loaded from the config file(s) are loaded _over_ these
Steven M. Gavac628a062002-01-19 10:33:21 +0000338 #defaults, so if there is a problem getting any core binding there will
Steven M. Gava17d01542001-12-03 00:37:28 +0000339 #be an 'ultimate last resort fallback' to the CUA-ish bindings
340 #defined here.
341 keyBindings={
342 '<<Copy>>': ['<Control-c>', '<Control-C>'],
343 '<<Cut>>': ['<Control-x>', '<Control-X>'],
344 '<<Paste>>': ['<Control-v>', '<Control-V>'],
345 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
346 '<<center-insert>>': ['<Control-l>'],
347 '<<close-all-windows>>': ['<Control-q>'],
348 '<<close-window>>': ['<Alt-F4>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000349 '<<end-of-file>>': ['<Control-d>'],
350 '<<python-docs>>': ['<F1>'],
351 '<<python-context-help>>': ['<Shift-F1>'],
352 '<<history-next>>': ['<Alt-n>'],
353 '<<history-previous>>': ['<Alt-p>'],
354 '<<interrupt-execution>>': ['<Control-c>'],
355 '<<open-class-browser>>': ['<Alt-c>'],
356 '<<open-module>>': ['<Alt-m>'],
357 '<<open-new-window>>': ['<Control-n>'],
358 '<<open-window-from-file>>': ['<Control-o>'],
359 '<<plain-newline-and-indent>>': ['<Control-j>'],
360 '<<redo>>': ['<Control-y>'],
361 '<<remove-selection>>': ['<Escape>'],
362 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
363 '<<save-window-as-file>>': ['<Alt-s>'],
364 '<<save-window>>': ['<Control-s>'],
365 '<<select-all>>': ['<Alt-a>'],
366 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000367 '<<undo>>': ['<Control-z>'],
368 '<<find-again>>': ['<Control-g>', '<F3>'],
369 '<<find-in-files>>': ['<Alt-F3>'],
370 '<<find-selection>>': ['<Control-F3>'],
371 '<<find>>': ['<Control-f>'],
372 '<<replace>>': ['<Control-h>'],
373 '<<goto-line>>': ['<Alt-g>'] }
374
Steven M. Gava17d01542001-12-03 00:37:28 +0000375 if keySetName:
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000376 for event in keyBindings.keys():
377 binding=self.GetKeyBinding(keySetName,event)
378 if binding: #otherwise will keep default
379 keyBindings[event]=binding
Steven M. Gava17d01542001-12-03 00:37:28 +0000380
381 return keyBindings
382
Steven M. Gava2a63a072001-10-26 06:50:54 +0000383
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000384 def LoadCfgFiles(self):
385 """
386 load all configuration files.
387 """
388 for key in self.defaultCfg.keys():
389 self.defaultCfg[key].Load()
390 self.userCfg[key].Load() #same keys
391
392 def SaveUserCfgFiles(self):
393 """
394 write all loaded user configuration files back to disk
395 """
396 for key in self.userCfg.keys():
397 self.userCfg[key].Save()
398
399idleConf=IdleConf()
400
401### module test
402if __name__ == '__main__':
403 def dumpCfg(cfg):
404 print '\n',cfg,'\n'
405 for key in cfg.keys():
406 sections=cfg[key].sections()
407 print key
408 print sections
409 for section in sections:
410 options=cfg[key].options(section)
411 print section
412 print options
413 for option in options:
414 print option, '=', cfg[key].Get(section,option)
415 dumpCfg(idleConf.defaultCfg)
416 dumpCfg(idleConf.userCfg)
417 print idleConf.userCfg['main'].Get('Theme','name')
418 #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')