blob: 358bee4803b0c1c821a4ab92653825d61de44a3f [file] [log] [blame]
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05001"""idlelib.config -- Manage IDLE configuration information.
Steven M. Gavac5976402002-01-04 03:06:08 +00002
Terry Jan Reedyf46b7822016-11-07 17:15:01 -05003The comments at the beginning of config-main.def describe the
4configuration files and the design implemented to update user
5configuration information. In particular, user configuration choices
6which duplicate the defaults will be removed from the user's
7configuration files, and if a user file becomes empty, it will be
8deleted.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +00009
Terry Jan Reedyf46b7822016-11-07 17:15:01 -050010The configuration database maps options to values. Comceptually, the
11database keys are tuples (config-type, section, item). As implemented,
12there are separate dicts for default and user values. Each has
13config-type keys 'main', 'extensions', 'highlight', and 'keys'. The
14value for each key is a ConfigParser instance that maps section and item
15to values. For 'main' and 'extenstons', user values override
16default values. For 'highlight' and 'keys', user sections augment the
17default sections (and must, therefore, have distinct names).
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000018
19Throughout this module there is an emphasis on returning useable defaults
20when a problem occurs in returning a requested configuration value back to
21idle. This is to allow IDLE to continue to function in spite of errors in
22the retrieval of config information. When a default is returned instead of
23a requested config value, a message is printed to stderr to aid in
24configuration problem notification and resolution.
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000025"""
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040026# TODOs added Oct 2014, tjr
27
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040028from configparser import ConfigParser
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +000029import os
30import sys
Guido van Rossum36e0a922007-07-20 04:05:57 +000031
Terry Jan Reedyd87d1682015-08-01 18:57:33 -040032from tkinter.font import Font, nametofont
Steven M. Gavac11ccf32001-09-24 09:43:17 +000033
Neal Norwitz5b0b00f2002-11-30 19:10:19 +000034class InvalidConfigType(Exception): pass
35class InvalidConfigSet(Exception): pass
36class InvalidFgBg(Exception): pass
37class InvalidTheme(Exception): pass
38
Steven M. Gavac11ccf32001-09-24 09:43:17 +000039class IdleConfParser(ConfigParser):
40 """
41 A ConfigParser specialised for idle configuration file handling
42 """
43 def __init__(self, cfgFile, cfgDefaults=None):
44 """
45 cfgFile - string, fully specified configuration file name
46 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040047 self.file = cfgFile
Serhiy Storchaka89953002013-02-07 15:24:36 +020048 ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000049
Thomas Wouterscf297e42007-02-23 15:07:44 +000050 def Get(self, section, option, type=None, default=None, raw=False):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000051 """
52 Get an option value for given section/option or return default.
53 If type is specified, return as type.
54 """
Terry Jan Reedya9421fb2014-10-22 20:15:18 -040055 # TODO Use default as fallback, at least if not None
56 # Should also print Warning(file, section, option).
57 # Currently may raise ValueError
Thomas Wouterscf297e42007-02-23 15:07:44 +000058 if not self.has_option(section, option):
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +000059 return default
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040060 if type == 'bool':
Thomas Wouterscf297e42007-02-23 15:07:44 +000061 return self.getboolean(section, option)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040062 elif type == 'int':
Thomas Wouterscf297e42007-02-23 15:07:44 +000063 return self.getint(section, option)
64 else:
65 return self.get(section, option, raw=raw)
Steven M. Gavac11ccf32001-09-24 09:43:17 +000066
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040067 def GetOptionList(self, section):
68 "Return a list of options for given section, else []."
Steven M. Gava085eb1b2002-02-05 04:52:32 +000069 if self.has_section(section):
Steven M. Gavac11ccf32001-09-24 09:43:17 +000070 return self.options(section)
71 else: #return a default value
72 return []
73
Steven M. Gavac11ccf32001-09-24 09:43:17 +000074 def Load(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040075 "Load the configuration file from disk."
Steven M. Gavac11ccf32001-09-24 09:43:17 +000076 self.read(self.file)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000077
Steven M. Gavac11ccf32001-09-24 09:43:17 +000078class IdleUserConfParser(IdleConfParser):
79 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000080 IdleConfigParser specialised for user configuration handling.
Steven M. Gavac11ccf32001-09-24 09:43:17 +000081 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000082
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040083 def AddSection(self, section):
84 "If section doesn't exist, add it."
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000085 if not self.has_section(section):
86 self.add_section(section)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000087
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000088 def RemoveEmptySections(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040089 "Remove any sections that have no options."
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000090 for section in self.sections():
91 if not self.GetOptionList(section):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000092 self.remove_section(section)
93
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000094 def IsEmpty(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040095 "Return True if no sections after removing empty sections."
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +000096 self.RemoveEmptySections()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040097 return not self.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000098
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -040099 def RemoveOption(self, section, option):
100 """Return True if option is removed from section, else False.
101
102 False if either section does not exist or did not have option.
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000103 """
104 if self.has_section(section):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400105 return self.remove_option(section, option)
106 return False
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000107
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400108 def SetOption(self, section, option, value):
109 """Return True if option is added or changed to value, else False.
110
111 Add section if required. False means option already had value.
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000112 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400113 if self.has_option(section, option):
114 if self.get(section, option) == value:
115 return False
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000116 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400117 self.set(section, option, value)
118 return True
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000119 else:
120 if not self.has_section(section):
121 self.add_section(section)
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400122 self.set(section, option, value)
123 return True
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000124
Steven M. Gavab77d3432002-03-02 07:16:21 +0000125 def RemoveFile(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400126 "Remove user config file self.file from disk if it exists."
Steven M. Gavab77d3432002-03-02 07:16:21 +0000127 if os.path.exists(self.file):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000128 os.remove(self.file)
129
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000130 def Save(self):
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000131 """Update user configuration file.
132
133 Remove empty sections. If resulting config isn't empty, write the file
134 to disk. If config is empty, remove the file from disk if it exists.
135
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000136 """
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000137 if not self.IsEmpty():
Christian Heimesbbffeb62008-01-24 09:42:52 +0000138 fname = self.file
139 try:
140 cfgFile = open(fname, 'w')
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200141 except OSError:
Christian Heimes0bd4e112008-02-12 22:59:25 +0000142 os.unlink(fname)
Christian Heimesbbffeb62008-01-24 09:42:52 +0000143 cfgFile = open(fname, 'w')
Amaury Forgeot d'Arcbbe7b0a2011-10-03 20:33:24 +0200144 with cfgFile:
145 self.write(cfgFile)
Steven M. Gava2d7bb3f2002-01-29 08:35:29 +0000146 else:
Steven M. Gavab77d3432002-03-02 07:16:21 +0000147 self.RemoveFile()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000148
149class IdleConf:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400150 """Hold config parsers for all idle config files in singleton instance.
151
152 Default config files, self.defaultCfg --
153 for config_type in self.config_types:
154 (idle install dir)/config-{config-type}.def
155
156 User config files, self.userCfg --
157 for config_type in self.config_types:
158 (user home dir)/.idlerc/config-{config-type}.cfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000159 """
160 def __init__(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400161 self.config_types = ('main', 'extensions', 'highlight', 'keys')
162 self.defaultCfg = {}
163 self.userCfg = {}
164 self.cfg = {} # TODO use to select userCfg vs defaultCfg
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000165 self.CreateConfigHandlers()
166 self.LoadCfgFiles()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400167
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000168
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000169 def CreateConfigHandlers(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400170 "Populate default and user config parser dictionaries."
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000171 #build idle install path
172 if __name__ != '__main__': # we were imported
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000173 idleDir=os.path.dirname(__file__)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000174 else: # we were exec'ed (for testing only)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000175 idleDir=os.path.abspath(sys.path[0])
176 userDir=self.GetUserCfgDir()
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400177
178 defCfgFiles = {}
179 usrCfgFiles = {}
180 # TODO eliminate these temporaries by combining loops
181 for cfgType in self.config_types: #build config file names
182 defCfgFiles[cfgType] = os.path.join(
183 idleDir, 'config-' + cfgType + '.def')
184 usrCfgFiles[cfgType] = os.path.join(
185 userDir, 'config-' + cfgType + '.cfg')
186 for cfgType in self.config_types: #create config parsers
187 self.defaultCfg[cfgType] = IdleConfParser(defCfgFiles[cfgType])
188 self.userCfg[cfgType] = IdleUserConfParser(usrCfgFiles[cfgType])
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000189
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000190 def GetUserCfgDir(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400191 """Return a filesystem directory for storing user config files.
Tim Peters608c2ff2005-01-13 17:37:38 +0000192
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400193 Creates it if required.
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000194 """
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000195 cfgDir = '.idlerc'
196 userDir = os.path.expanduser('~')
197 if userDir != '~': # expanduser() found user home dir
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000198 if not os.path.exists(userDir):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400199 warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
200 userDir + ',\n but the path does not exist.')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000201 try:
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400202 print(warn, file=sys.stderr)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200203 except OSError:
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000204 pass
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000205 userDir = '~'
206 if userDir == "~": # still no path to home!
207 # traditionally IDLE has defaulted to os.getcwd(), is this adequate?
208 userDir = os.getcwd()
209 userDir = os.path.join(userDir, cfgDir)
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000210 if not os.path.exists(userDir):
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000211 try:
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000212 os.mkdir(userDir)
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200213 except OSError:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400214 warn = ('\n Warning: unable to create user config directory\n' +
215 userDir + '\n Check path and permissions.\n Exiting!\n')
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400216 print(warn, file=sys.stderr)
Kurt B. Kaiser1b6f3982005-01-11 19:29:39 +0000217 raise SystemExit
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400218 # TODO continue without userDIr instead of exit
Steven M. Gava7cff66d2002-02-01 03:02:37 +0000219 return userDir
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000220
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000221 def GetOption(self, configType, section, option, default=None, type=None,
Thomas Wouterscf297e42007-02-23 15:07:44 +0000222 warn_on_default=True, raw=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400223 """Return a value for configType section option, or default.
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000224
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400225 If type is not None, return a value of that type. Also pass raw
226 to the config parser. First try to return a valid value
227 (including type) from a user configuration. If that fails, try
228 the default configuration. If that fails, return default, with a
229 default of None.
230
231 Warn if either user or default configurations have an invalid value.
232 Warn if default is returned and warn_on_default is True.
Steven M. Gava429a86af2001-10-23 10:42:12 +0000233 """
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200234 try:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400235 if self.userCfg[configType].has_option(section, option):
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200236 return self.userCfg[configType].Get(section, option,
237 type=type, raw=raw)
238 except ValueError:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400239 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200240 ' invalid %r value for configuration option %r\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400241 ' from section %r: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200242 (type, option, section,
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400243 self.userCfg[configType].Get(section, option, raw=raw)))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400244 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200245 try:
246 if self.defaultCfg[configType].has_option(section,option):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400247 return self.defaultCfg[configType].Get(
248 section, option, type=type, raw=raw)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200249 except ValueError:
250 pass
251 #returning default, print warning
252 if warn_on_default:
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400253 warning = ('\n Warning: config.py - IdleConf.GetOption -\n'
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200254 ' problem retrieving configuration option %r\n'
255 ' from section %r.\n'
Terry Jan Reedy81b062f2014-09-19 22:38:41 -0400256 ' returning default value: %r' %
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200257 (option, section, default))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400258 _warn(warning, configType, section, option)
Andrew Svetlov8a495a42012-12-24 13:15:43 +0200259 return default
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000260
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000261 def SetOption(self, configType, section, option, value):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400262 """Set section option to value in user config file."""
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000263 self.userCfg[configType].SetOption(section, option, value)
264
Steven M. Gava2a63a072001-10-26 06:50:54 +0000265 def GetSectionList(self, configSet, configType):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400266 """Return sections for configSet configType configuration.
267
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000268 configSet must be either 'user' or 'default'
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400269 configType must be in self.config_types.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000270 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400271 if not (configType in self.config_types):
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000272 raise InvalidConfigType('Invalid configType specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000273 if configSet == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400274 cfgParser = self.userCfg[configType]
Steven M. Gava2a63a072001-10-26 06:50:54 +0000275 elif configSet == 'default':
276 cfgParser=self.defaultCfg[configType]
277 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000278 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava2a63a072001-10-26 06:50:54 +0000279 return cfgParser.sections()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000280
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000281 def GetHighlight(self, theme, element, fgBg=None):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400282 """Return individual theme element highlight color(s).
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400283
Terry Jan Reedy86757992014-10-09 18:44:32 -0400284 fgBg - string ('fg' or 'bg') or None.
285 If None, return a dictionary containing fg and bg colors with
286 keys 'foreground' and 'background'. Otherwise, only return
287 fg or bg color, as specified. Colors are intended to be
288 appropriate for passing to Tkinter in, e.g., a tag_config call).
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000289 """
Steven M. Gava9f25e672002-02-11 02:51:18 +0000290 if self.defaultCfg['highlight'].has_section(theme):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400291 themeDict = self.GetThemeDict('default', theme)
Steven M. Gava9f25e672002-02-11 02:51:18 +0000292 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400293 themeDict = self.GetThemeDict('user', theme)
294 fore = themeDict[element + '-foreground']
Terry Jan Reedy86757992014-10-09 18:44:32 -0400295 if element == 'cursor': # There is no config value for cursor bg
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400296 back = themeDict['normal-background']
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000297 else:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400298 back = themeDict[element + '-background']
299 highlight = {"foreground": fore, "background": back}
Terry Jan Reedy86757992014-10-09 18:44:32 -0400300 if not fgBg: # Return dict of both colors
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000301 return highlight
Terry Jan Reedy86757992014-10-09 18:44:32 -0400302 else: # Return specified color only
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000303 if fgBg == 'fg':
304 return highlight["foreground"]
305 if fgBg == 'bg':
306 return highlight["background"]
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000307 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000308 raise InvalidFgBg('Invalid fgBg specified')
Steven M. Gava9f25e672002-02-11 02:51:18 +0000309
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400310 def GetThemeDict(self, type, themeName):
311 """Return {option:value} dict for elements in themeName.
312
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000313 type - string, 'default' or 'user' theme type
314 themeName - string, theme name
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400315 Values are loaded over ultimate fallback defaults to guarantee
316 that all theme elements are present in a newly created theme.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000317 """
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000318 if type == 'user':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400319 cfgParser = self.userCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000320 elif type == 'default':
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400321 cfgParser = self.defaultCfg['highlight']
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000322 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000323 raise InvalidTheme('Invalid theme type specified')
Terry Jan Reedy86757992014-10-09 18:44:32 -0400324 # Provide foreground and background colors for each theme
325 # element (other than cursor) even though some values are not
326 # yet used by idle, to allow for their use in the future.
327 # Default values are generally black and white.
328 # TODO copy theme from a class attribute.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400329 theme ={'normal-foreground':'#000000',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000330 'normal-background':'#ffffff',
331 'keyword-foreground':'#000000',
332 'keyword-background':'#ffffff',
Kurt B. Kaiser73360a32004-03-08 18:15:31 +0000333 'builtin-foreground':'#000000',
334 'builtin-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000335 'comment-foreground':'#000000',
336 'comment-background':'#ffffff',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000337 'string-foreground':'#000000',
338 'string-background':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000339 'definition-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000340 'definition-background':'#ffffff',
341 'hilite-foreground':'#000000',
342 'hilite-background':'gray',
343 'break-foreground':'#ffffff',
344 'break-background':'#000000',
345 'hit-foreground':'#ffffff',
346 'hit-background':'#000000',
347 'error-foreground':'#ffffff',
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000348 'error-background':'#000000',
349 #cursor (only foreground can be set)
350 'cursor-foreground':'#000000',
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000351 #shell window
352 'stdout-foreground':'#000000',
353 'stdout-background':'#ffffff',
354 'stderr-foreground':'#000000',
355 'stderr-background':'#ffffff',
356 'console-foreground':'#000000',
357 'console-background':'#ffffff' }
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000358 for element in theme:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400359 if not cfgParser.has_option(themeName, element):
Terry Jan Reedy86757992014-10-09 18:44:32 -0400360 # Print warning that will return a default color
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400361 warning = ('\n Warning: config.IdleConf.GetThemeDict'
Walter Dörwald70a6b492004-02-12 17:35:32 +0000362 ' -\n problem retrieving theme element %r'
363 '\n from theme %r.\n'
Terry Jan Reedy86757992014-10-09 18:44:32 -0400364 ' returning default color: %r' %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000365 (element, themeName, theme[element]))
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400366 _warn(warning, 'highlight', themeName, element)
Terry Jan Reedy86757992014-10-09 18:44:32 -0400367 theme[element] = cfgParser.Get(
368 themeName, element, default=theme[element])
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000369 return theme
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000370
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000371 def CurrentTheme(self):
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400372 "Return the name of the currently active text color theme."
373 return self.current_colors_and_keys('Theme')
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500374
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400375 def CurrentKeys(self):
376 """Return the name of the currently active key set."""
377 return self.current_colors_and_keys('Keys')
378
379 def current_colors_and_keys(self, section):
380 """Return the currently active name for Theme or Keys section.
381
382 idlelib.config-main.def ('default') includes these sections
383
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500384 [Theme]
385 default= 1
386 name= IDLE Classic
387 name2=
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500388
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400389 [Keys]
390 default= 1
391 name=
392 name2=
393
394 Item 'name2', is used for built-in ('default') themes and keys
395 added after 2015 Oct 1 and 2016 July 1. This kludge is needed
396 because setting 'name' to a builtin not defined in older IDLEs
397 to display multiple error messages or quit.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500398 See https://bugs.python.org/issue25313.
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400399 When default = True, 'name2' takes precedence over 'name',
400 while older IDLEs will just use name. When default = False,
401 'name2' may still be set, but it is ignored.
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500402 """
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400403 cfgname = 'highlight' if section == 'Theme' else 'keys'
Terry Jan Reedy5acf4e52016-08-24 22:08:01 -0400404 default = self.GetOption('main', section, 'default',
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500405 type='bool', default=True)
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400406 name = ''
Terry Jan Reedyd0c0f002015-11-12 15:02:57 -0500407 if default:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400408 name = self.GetOption('main', section, 'name2', default='')
409 if not name:
410 name = self.GetOption('main', section, 'name', default='')
411 if name:
412 source = self.defaultCfg if default else self.userCfg
413 if source[cfgname].has_section(name):
414 return name
415 return "IDLE Classic" if section == 'Theme' else self.default_keys()
416
417 @staticmethod
418 def default_keys():
419 if sys.platform[:3] == 'win':
420 return 'IDLE Classic Windows'
421 elif sys.platform == 'darwin':
422 return 'IDLE Classic OSX'
Terry Jan Reedyc15a7c62015-11-12 15:06:07 -0500423 else:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400424 return 'IDLE Modern Unix'
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000425
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400426 def GetExtensions(self, active_only=True,
427 editor_only=False, shell_only=False):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400428 """Return extensions in default and user config-extensions files.
429
430 If active_only True, only return active (enabled) extensions
431 and optionally only editor or shell extensions.
432 If active_only False, return all extensions.
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000433 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400434 extns = self.RemoveKeyBindNames(
435 self.GetSectionList('default', 'extensions'))
436 userExtns = self.RemoveKeyBindNames(
437 self.GetSectionList('user', 'extensions'))
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000438 for extn in userExtns:
439 if extn not in extns: #user has added own extension
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000440 extns.append(extn)
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000441 if active_only:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400442 activeExtns = []
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000443 for extn in extns:
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000444 if self.GetOption('extensions', extn, 'enable', default=True,
445 type='bool'):
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000446 #the extension is enabled
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400447 if editor_only or shell_only: # TODO both True contradict
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000448 if editor_only:
449 option = "enable_editor"
450 else:
451 option = "enable_shell"
452 if self.GetOption('extensions', extn,option,
453 default=True, type='bool',
454 warn_on_default=False):
455 activeExtns.append(extn)
456 else:
457 activeExtns.append(extn)
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000458 return activeExtns
459 else:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000460 return extns
Steven M. Gavaad4f5322002-01-03 12:05:17 +0000461
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400462 def RemoveKeyBindNames(self, extnNameList):
463 "Return extnNameList with keybinding section names removed."
464 # TODO Easier to return filtered copy with list comp
465 names = extnNameList
466 kbNameIndicies = []
Steven M. Gavac628a062002-01-19 10:33:21 +0000467 for name in names:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000468 if name.endswith(('_bindings', '_cfgBindings')):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000469 kbNameIndicies.append(names.index(name))
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400470 kbNameIndicies.sort(reverse=True)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000471 for index in kbNameIndicies: #delete each keybinding section name
Steven M. Gavac628a062002-01-19 10:33:21 +0000472 del(names[index])
473 return names
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000474
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400475 def GetExtnNameForEvent(self, virtualEvent):
476 """Return the name of the extension binding virtualEvent, or None.
477
478 virtualEvent - string, name of the virtual event to test for,
479 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000480 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400481 extName = None
482 vEvent = '<<' + virtualEvent + '>>'
Kurt B. Kaiser4d5bc602004-06-06 01:29:22 +0000483 for extn in self.GetExtensions(active_only=0):
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000484 for event in self.GetExtensionKeys(extn):
Steven M. Gavaa498af22002-02-01 01:33:36 +0000485 if event == vEvent:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400486 extName = extn # TODO return here?
Steven M. Gavaa498af22002-02-01 01:33:36 +0000487 return extName
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000488
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400489 def GetExtensionKeys(self, extensionName):
490 """Return dict: {configurable extensionName event : active keybinding}.
491
492 Events come from default config extension_cfgBindings section.
493 Keybindings come from GetCurrentKeySet() active key dict,
494 where previously used bindings are disabled.
Steven M. Gavac628a062002-01-19 10:33:21 +0000495 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400496 keysName = extensionName + '_cfgBindings'
497 activeKeys = self.GetCurrentKeySet()
498 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000499 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400500 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000501 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400502 event = '<<' + eventName + '>>'
503 binding = activeKeys[event]
504 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000505 return extKeys
506
Steven M. Gavac628a062002-01-19 10:33:21 +0000507 def __GetRawExtensionKeys(self,extensionName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400508 """Return dict {configurable extensionName event : keybinding list}.
509
510 Events come from default config extension_cfgBindings section.
511 Keybindings list come from the splitting of GetOption, which
512 tries user config before default config.
Steven M. Gavac628a062002-01-19 10:33:21 +0000513 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400514 keysName = extensionName+'_cfgBindings'
515 extKeys = {}
Steven M. Gavac628a062002-01-19 10:33:21 +0000516 if self.defaultCfg['extensions'].has_section(keysName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400517 eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000518 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400519 binding = self.GetOption(
520 'extensions', keysName, eventName, default='').split()
521 event = '<<' + eventName + '>>'
522 extKeys[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000523 return extKeys
524
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400525 def GetExtensionBindings(self, extensionName):
526 """Return dict {extensionName event : active or defined keybinding}.
527
528 Augment self.GetExtensionKeys(extensionName) with mapping of non-
529 configurable events (from default config) to GetOption splits,
530 as in self.__GetRawExtensionKeys.
Steven M. Gavac628a062002-01-19 10:33:21 +0000531 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400532 bindsName = extensionName + '_bindings'
533 extBinds = self.GetExtensionKeys(extensionName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000534 #add the non-configurable bindings
535 if self.defaultCfg['extensions'].has_section(bindsName):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400536 eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
Steven M. Gavac628a062002-01-19 10:33:21 +0000537 for eventName in eventNames:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400538 binding = self.GetOption(
539 'extensions', bindsName, eventName, default='').split()
540 event = '<<' + eventName + '>>'
541 extBinds[event] = binding
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000542
543 return extBinds
544
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000545 def GetKeyBinding(self, keySetName, eventStr):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400546 """Return the keybinding list for keySetName eventStr.
547
548 keySetName - name of key binding set (config-keys section).
549 eventStr - virtual event, including brackets, as in '<<event>>'.
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000550 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400551 eventName = eventStr[2:-2] #trim off the angle brackets
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400552 binding = self.GetOption('keys', keySetName, eventName, default='',
553 warn_on_default=False).split()
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000554 return binding
555
Steven M. Gavac628a062002-01-19 10:33:21 +0000556 def GetCurrentKeySet(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400557 "Return CurrentKeys with 'darwin' modifications."
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000558 result = self.GetKeySet(self.CurrentKeys())
559
Ned Deilyb7601672014-03-27 20:49:14 -0700560 if sys.platform == "darwin":
561 # OS X Tk variants do not support the "Alt" keyboard modifier.
562 # So replace all keybingings that use "Alt" with ones that
563 # use the "Option" keyboard modifier.
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400564 # TODO (Ned?): the "Option" modifier does not work properly for
Ned Deilyb7601672014-03-27 20:49:14 -0700565 # Cocoa Tk and XQuartz Tk so we should not use it
566 # in default OS X KeySets.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000567 for k, v in result.items():
568 v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
569 if v != v2:
570 result[k] = v2
571
572 return result
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000573
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400574 def GetKeySet(self, keySetName):
575 """Return event-key dict for keySetName core plus active extensions.
576
577 If a binding defined in an extension is already in use, the
578 extension binding is disabled by being set to ''
Steven M. Gava2a63a072001-10-26 06:50:54 +0000579 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400580 keySet = self.GetCoreKeys(keySetName)
581 activeExtns = self.GetExtensions(active_only=1)
Steven M. Gavac628a062002-01-19 10:33:21 +0000582 for extn in activeExtns:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400583 extKeys = self.__GetRawExtensionKeys(extn)
Steven M. Gavac628a062002-01-19 10:33:21 +0000584 if extKeys: #the extension defines keybindings
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000585 for event in extKeys:
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000586 if extKeys[event] in keySet.values():
Steven M. Gavac628a062002-01-19 10:33:21 +0000587 #the binding is already in use
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400588 extKeys[event] = '' #disable this binding
589 keySet[event] = extKeys[event] #add binding
Steven M. Gavaf9bb90e2002-01-24 06:02:50 +0000590 return keySet
591
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400592 def IsCoreBinding(self, virtualEvent):
593 """Return True if the virtual event is one of the core idle key events.
594
595 virtualEvent - string, name of the virtual event to test for,
596 without the enclosing '<< >>'
Steven M. Gavaa498af22002-02-01 01:33:36 +0000597 """
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000598 return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000599
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400600# TODO make keyBindins a file or class attribute used for test above
601# and copied in function below
602
Steven M. Gavac628a062002-01-19 10:33:21 +0000603 def GetCoreKeys(self, keySetName=None):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400604 """Return dict of core virtual-key keybindings for keySetName.
605
606 The default keySetName None corresponds to the keyBindings base
607 dict. If keySetName is not None, bindings from the config
608 file(s) are loaded _over_ these defaults, so if there is a
609 problem getting any core binding there will be an 'ultimate last
610 resort fallback' to the CUA-ish bindings defined here.
Steven M. Gava2a63a072001-10-26 06:50:54 +0000611 """
Steven M. Gava17d01542001-12-03 00:37:28 +0000612 keyBindings={
Steven M. Gavaa498af22002-02-01 01:33:36 +0000613 '<<copy>>': ['<Control-c>', '<Control-C>'],
614 '<<cut>>': ['<Control-x>', '<Control-X>'],
615 '<<paste>>': ['<Control-v>', '<Control-V>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000616 '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
617 '<<center-insert>>': ['<Control-l>'],
618 '<<close-all-windows>>': ['<Control-q>'],
619 '<<close-window>>': ['<Alt-F4>'],
Kurt B. Kaiser84f48032002-09-26 22:13:22 +0000620 '<<do-nothing>>': ['<Control-x>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000621 '<<end-of-file>>': ['<Control-d>'],
622 '<<python-docs>>': ['<F1>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000623 '<<python-context-help>>': ['<Shift-F1>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000624 '<<history-next>>': ['<Alt-n>'],
625 '<<history-previous>>': ['<Alt-p>'],
626 '<<interrupt-execution>>': ['<Control-c>'],
Kurt B. Kaiser1061e722003-01-04 01:43:53 +0000627 '<<view-restart>>': ['<F6>'],
Kurt B. Kaiser4cc5ef52003-01-22 00:23:23 +0000628 '<<restart-shell>>': ['<Control-F6>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000629 '<<open-class-browser>>': ['<Alt-c>'],
630 '<<open-module>>': ['<Alt-m>'],
631 '<<open-new-window>>': ['<Control-n>'],
632 '<<open-window-from-file>>': ['<Control-o>'],
633 '<<plain-newline-and-indent>>': ['<Control-j>'],
Steven M. Gava7981ce52002-06-11 04:45:34 +0000634 '<<print-window>>': ['<Control-p>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000635 '<<redo>>': ['<Control-y>'],
636 '<<remove-selection>>': ['<Escape>'],
Kurt B. Kaiser2303b1c2003-11-24 05:26:16 +0000637 '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
Steven M. Gava17d01542001-12-03 00:37:28 +0000638 '<<save-window-as-file>>': ['<Alt-s>'],
639 '<<save-window>>': ['<Control-s>'],
640 '<<select-all>>': ['<Alt-a>'],
641 '<<toggle-auto-coloring>>': ['<Control-slash>'],
Steven M. Gava0cae01c2002-01-04 07:53:06 +0000642 '<<undo>>': ['<Control-z>'],
643 '<<find-again>>': ['<Control-g>', '<F3>'],
644 '<<find-in-files>>': ['<Alt-F3>'],
645 '<<find-selection>>': ['<Control-F3>'],
646 '<<find>>': ['<Control-f>'],
647 '<<replace>>': ['<Control-h>'],
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000648 '<<goto-line>>': ['<Alt-g>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000649 '<<smart-backspace>>': ['<Key-BackSpace>'],
Andrew Svetlov67ac0792012-03-29 19:01:28 +0300650 '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000651 '<<smart-indent>>': ['<Key-Tab>'],
652 '<<indent-region>>': ['<Control-Key-bracketright>'],
653 '<<dedent-region>>': ['<Control-Key-bracketleft>'],
654 '<<comment-region>>': ['<Alt-Key-3>'],
655 '<<uncomment-region>>': ['<Alt-Key-4>'],
656 '<<tabify-region>>': ['<Alt-Key-5>'],
657 '<<untabify-region>>': ['<Alt-Key-6>'],
658 '<<toggle-tabs>>': ['<Alt-Key-t>'],
Kurt B. Kaiser3069dbb2005-01-28 00:16:16 +0000659 '<<change-indentwidth>>': ['<Alt-Key-u>'],
660 '<<del-word-left>>': ['<Control-Key-BackSpace>'],
661 '<<del-word-right>>': ['<Control-Key-Delete>']
Kurt B. Kaisera9f8cbc2002-09-14 03:17:01 +0000662 }
Steven M. Gava17d01542001-12-03 00:37:28 +0000663 if keySetName:
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400664 if not (self.userCfg['keys'].has_section(keySetName) or
665 self.defaultCfg['keys'].has_section(keySetName)):
666 warning = (
667 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
668 ' key set %r is not defined, using default bindings.' %
669 (keySetName,)
670 )
671 _warn(warning, 'keys', keySetName)
672 else:
673 for event in keyBindings:
674 binding = self.GetKeyBinding(keySetName, event)
675 if binding:
676 keyBindings[event] = binding
677 else: #we are going to return a default, print warning
678 warning = (
679 '\n Warning: config.py - IdleConf.GetCoreKeys -\n'
680 ' problem retrieving key binding for event %r\n'
681 ' from key set %r.\n'
682 ' returning default value: %r' %
683 (event, keySetName, keyBindings[event])
684 )
685 _warn(warning, 'keys', keySetName, event)
Steven M. Gava17d01542001-12-03 00:37:28 +0000686 return keyBindings
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000687
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400688 def GetExtraHelpSourceList(self, configSet):
689 """Return list of extra help sources from a given configSet.
Kurt B. Kaisere66675b2003-01-27 02:36:18 +0000690
Kurt B. Kaiser8e92bf72003-01-14 22:03:31 +0000691 Valid configSets are 'user' or 'default'. Return a list of tuples of
692 the form (menu_item , path_to_help_file , option), or return the empty
693 list. 'option' is the sequence number of the help resource. 'option'
694 values determine the position of the menu items on the Help menu,
695 therefore the returned list must be sorted by 'option'.
696
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000697 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400698 helpSources = []
699 if configSet == 'user':
700 cfgParser = self.userCfg['main']
701 elif configSet == 'default':
702 cfgParser = self.defaultCfg['main']
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000703 else:
Kurt B. Kaiserad667422007-08-23 01:06:15 +0000704 raise InvalidConfigSet('Invalid configSet specified')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000705 options=cfgParser.GetOptionList('HelpFiles')
706 for option in options:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400707 value=cfgParser.Get('HelpFiles', option, default=';')
708 if value.find(';') == -1: #malformed config entry with no ';'
709 menuItem = '' #make these empty
710 helpPath = '' #so value won't be added to list
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000711 else: #config entry contains ';' as expected
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000712 value=value.split(';')
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000713 menuItem=value[0].strip()
714 helpPath=value[1].strip()
715 if menuItem and helpPath: #neither are empty strings
716 helpSources.append( (menuItem,helpPath,option) )
Kurt B. Kaiser4718bf82008-02-12 21:34:12 +0000717 helpSources.sort(key=lambda x: x[2])
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000718 return helpSources
719
720 def GetAllExtraHelpSourcesList(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400721 """Return a list of the details of all additional help sources.
722
723 Tuples in the list are those of GetExtraHelpSourceList.
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000724 """
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400725 allHelpSources = (self.GetExtraHelpSourceList('default') +
Steven M. Gava085eb1b2002-02-05 04:52:32 +0000726 self.GetExtraHelpSourceList('user') )
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000727 return allHelpSources
728
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400729 def GetFont(self, root, configType, section):
730 """Retrieve a font from configuration (font, font-size, font-bold)
731 Intercept the special value 'TkFixedFont' and substitute
732 the actual font, factoring in some tweaks if needed for
733 appearance sakes.
734
735 The 'root' parameter can normally be any valid Tkinter widget.
736
737 Return a tuple (family, size, weight) suitable for passing
738 to tkinter.Font
739 """
740 family = self.GetOption(configType, section, 'font', default='courier')
741 size = self.GetOption(configType, section, 'font-size', type='int',
742 default='10')
743 bold = self.GetOption(configType, section, 'font-bold', default=0,
744 type='bool')
745 if (family == 'TkFixedFont'):
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400746 f = Font(name='TkFixedFont', exists=True, root=root)
747 actualFont = Font.actual(f)
748 family = actualFont['family']
749 size = actualFont['size']
750 if size <= 0:
751 size = 10 # if font in pixels, ignore actual size
752 bold = actualFont['weight'] == 'bold'
Terry Jan Reedyd87d1682015-08-01 18:57:33 -0400753 return (family, size, 'bold' if bold else 'normal')
754
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000755 def LoadCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400756 "Load all configuration files."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000757 for key in self.defaultCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000758 self.defaultCfg[key].Load()
759 self.userCfg[key].Load() #same keys
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000760
761 def SaveUserCfgFiles(self):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400762 "Write all loaded user configuration files to disk."
Kurt B. Kaisere0712772007-08-23 05:25:55 +0000763 for key in self.userCfg:
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000764 self.userCfg[key].Save()
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000765
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000766
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400767idleConf = IdleConf()
768
Terry Jan Reedy9bdb1ed2016-07-10 13:46:34 -0400769
770_warned = set()
771def _warn(msg, *key):
772 key = (msg,) + key
773 if key not in _warned:
774 try:
775 print(msg, file=sys.stderr)
776 except OSError:
777 pass
778 _warned.add(key)
779
780
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400781# TODO Revise test output, write expanded unittest
Terry Jan Reedy1080d132016-06-09 21:09:15 -0400782#
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000783if __name__ == '__main__':
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400784 from zlib import crc32
785 line, crc = 0, 0
786
787 def sprint(obj):
788 global line, crc
789 txt = str(obj)
790 line += 1
791 crc = crc32(txt.encode(encoding='utf-8'), crc)
792 print(txt)
793 #print('***', line, crc, '***') # uncomment for diagnosis
794
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000795 def dumpCfg(cfg):
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400796 print('\n', cfg, '\n') # has variable '0xnnnnnnnn' addresses
797 for key in sorted(cfg.keys()):
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400798 sections = cfg[key].sections()
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400799 sprint(key)
800 sprint(sections)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000801 for section in sections:
Terry Jan Reedydeb7bf12014-10-06 23:26:26 -0400802 options = cfg[key].options(section)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400803 sprint(section)
804 sprint(options)
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000805 for option in options:
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400806 sprint(option + ' = ' + cfg[key].Get(section, option))
807
Steven M. Gavac11ccf32001-09-24 09:43:17 +0000808 dumpCfg(idleConf.defaultCfg)
809 dumpCfg(idleConf.userCfg)
Terry Jan Reedy2279aeb2016-07-05 20:09:53 -0400810 print('\nlines = ', line, ', crc = ', crc, sep='')