b16b93e343fb65596a52606640eb9b0fa58331aa
[enigma2.git] / lib / python / Components / config.py
1 import time
2 from Tools.NumericalTextInput import NumericalTextInput
3 from Tools.Directories import resolveFilename, SCOPE_CONFIG
4 import copy
5
6
7 # ConfigElement, the base class of all ConfigElements.
8
9 # it stores:
10 #   value    the current value, usefully encoded.
11 #            usually a property which retrieves _value,
12 #            and maybe does some reformatting
13 #   _value   the value as it's going to be saved in the configfile,
14 #            though still in non-string form.
15 #            this is the object which is actually worked on.
16 #   default  the initial value. If _value is equal to default,
17 #            it will not be stored in the config file
18 #   saved_value is a text representation of _value, stored in the config file
19 #
20 # and has (at least) the following methods:
21 #   save()   stores _value into saved_value, 
22 #            (or stores 'None' if it should not be stored)
23 #   load()   loads _value from saved_value, or loads
24 #            the default if saved_value is 'None' (default)
25 #            or invalid.
26 #
27 class ConfigElement(object):
28         def __init__(self):
29
30                 object.__init__(self)
31                 self.saved_value = None
32                 self.save_disabled = False
33                 self.notifiers = []
34                 self.enabled = True
35
36         # you need to override this to do input validation
37         def setValue(self, value):
38                 self._value = value
39                 self.changed()
40
41         def getValue(self):
42                 return self._value
43         
44         value = property(getValue, setValue)
45
46         # you need to override this if self.value is not a string
47         def fromstring(self, value):
48                 return value
49
50         # you can overide this for fancy default handling
51         def load(self):
52                 if self.saved_value is None:
53                         self.value = self.default
54                 else:
55                         self.value = self.fromstring(self.saved_value)
56
57         def tostring(self, value):
58                 return str(value)
59
60         # you need to override this if str(self.value) doesn't work
61         def save(self):
62                 if self.save_disabled or self.value == self.default:
63                         self.saved_value = None
64                 else:
65                         self.saved_value = self.tostring(self.value)
66
67         def cancel(self):
68                 self.load()
69
70         def changed(self):
71                 for x in self.notifiers:
72                         x(self)
73                         
74         def addNotifier(self, notifier, initial_call = True):
75                 assert callable(notifier), "notifiers must be callable"
76                 self.notifiers.append(notifier)
77
78                 # CHECKME:
79                 # do we want to call the notifier
80                 #  - at all when adding it? (yes, though optional)
81                 #  - when the default is active? (yes)
82                 #  - when no value *yet* has been set,
83                 #    because no config has ever been read (currently yes)
84                 #    (though that's not so easy to detect.
85                 #     the entry could just be new.)
86                 if initial_call:
87                         notifier(self)
88
89         def disableSave(self):
90                 self.save_disabled = True
91
92         def __call__(self, selected):
93                 return self.getMulti(selected)
94
95         def helpWindow(self):
96                 return None
97
98 KEY_LEFT = 0
99 KEY_RIGHT = 1
100 KEY_OK = 2
101 KEY_DELETE = 3
102 KEY_TIMEOUT = 4
103 KEY_NUMBERS = range(12, 12+10)
104 KEY_0 = 12
105 KEY_9 = 12+9
106
107 def getKeyNumber(key):
108         assert key in KEY_NUMBERS
109         return key - KEY_0
110
111 #
112 # ConfigSelection is a "one of.."-type.
113 # it has the "choices", usually a list, which contains
114 # (id, desc)-tuples (or just only the ids, in case the id
115 # will be used as description)
116 #
117 # all ids MUST be plain strings.
118 #
119 class ConfigSelection(ConfigElement):
120         def __init__(self, choices, default = None):
121                 ConfigElement.__init__(self)
122                 self.choices = []
123                 self.description = {}
124                 
125                 if isinstance(choices, list):
126                         for x in choices:
127                                 if isinstance(x, tuple):
128                                         self.choices.append(x[0])
129                                         self.description[x[0]] = x[1]
130                                 else:
131                                         self.choices.append(x)
132                                         self.description[x] = x
133                 elif isinstance(choices, dict):
134                         for (key, val) in choices.items():
135                                 self.choices.append(key)
136                                 self.description[key] = val
137                 else:
138                         assert False, "ConfigSelection choices must be dict or list!"
139                 
140                 #assert len(self.choices), "you can't have an empty configselection"
141                 if len(self.choices) == 0:
142                         self.choices = [""]
143                         self.description[""] = ""
144
145                 if default is None:
146                         default = self.choices[0]
147
148                 assert default in self.choices, "default must be in choice list, but " + repr(default) + " is not!"
149                 for x in self.choices:
150                         assert isinstance(x, str), "ConfigSelection choices must be strings"
151                 
152                 self.value = self.default = default
153
154         def setValue(self, value):
155                 if value in self.choices:
156                         self._value = value
157                 else:
158                         self._value = self.default
159                 
160                 self.changed()
161
162         def tostring(self, val):
163                 return val
164
165         def getValue(self):
166                 return self._value
167
168         value = property(getValue, setValue)
169         
170         def getIndex(self):
171                 return self.choices.index(self.value)
172         
173         index = property(getIndex)
174
175         # GUI
176         def handleKey(self, key):
177                 nchoices = len(self.choices)
178                 i = self.choices.index(self.value)
179                 if key == KEY_LEFT:
180                         self.value = self.choices[(i + nchoices - 1) % nchoices]
181                 elif key == KEY_RIGHT:
182                         self.value = self.choices[(i + 1) % nchoices]
183
184         def getMulti(self, selected):
185                 return ("text", self.description[self.value])
186
187         # HTML
188         def getHTML(self, id):
189                 res = ""
190                 for v in self.choices:
191                         if self.value == v:
192                                 checked = 'checked="checked" '
193                         else:
194                                 checked = ''
195                         res += '<input type="radio" name="' + id + '" ' + checked + 'value="' + v + '">' + self.description[v] + "</input></br>\n"
196                 return res;
197
198         def unsafeAssign(self, value):
199                 # setValue does check if value is in choices. This is safe enough.
200                 self.value = value
201
202 # a binary decision.
203 #
204 # several customized versions exist for different
205 # descriptions.
206 #
207 class ConfigBoolean(ConfigElement):
208         def __init__(self, default = False, descriptions = {False: "false", True: "true"}):
209                 ConfigElement.__init__(self)
210                 self.descriptions = descriptions
211                 self.value = self.default = default
212         def handleKey(self, key):
213                 if key in [KEY_LEFT, KEY_RIGHT]:
214                         self.value = not self.value
215
216         def getMulti(self, selected):
217                 return ("text", _(self.descriptions[self.value]))
218
219         def tostring(self, value):
220                 if not value:
221                         return "false"
222                 else:
223                         return "true"
224
225         def fromstring(self, val):
226                 if val == "true":
227                         return True
228                 else:
229                         return False
230
231         def getHTML(self, id):
232                 if self.value:
233                         checked = ' checked="checked"'
234                 else:
235                         checked = ''
236                 return '<input type="checkbox" name="' + id + '" value="1" ' + checked + " />"
237
238         # this is FLAWED. and must be fixed.
239         def unsafeAssign(self, value):
240                 if value == "1":
241                         self.value = True
242                 else:
243                         self.value = False
244
245 class ConfigYesNo(ConfigBoolean):
246         def __init__(self, default = False):
247                 ConfigBoolean.__init__(self, default = default, descriptions = {False: _("no"), True: _("yes")})
248
249 class ConfigOnOff(ConfigBoolean):
250         def __init__(self, default = False):
251                 ConfigBoolean.__init__(self, default = default, descriptions = {False: _("off"), True: _("on")})
252
253 class ConfigEnableDisable(ConfigBoolean):
254         def __init__(self, default = False):
255                 ConfigBoolean.__init__(self, default = default, descriptions = {False: _("disable"), True: _("enable")})
256
257 class ConfigDateTime(ConfigElement):
258         def __init__(self, default, formatstring, increment = 86400):
259                 ConfigElement.__init__(self)
260                 self.increment = increment
261                 self.formatstring = formatstring
262                 self.value = self.default = int(default)
263
264         def handleKey(self, key):
265                 if key == KEY_LEFT:
266                         self.value = self.value - self.increment
267                 if key == KEY_RIGHT:
268                         self.value = self.value + self.increment
269
270         def getMulti(self, selected):
271                 return ("text", time.strftime(self.formatstring, time.localtime(self.value)))
272
273         def fromstring(self, val):
274                 return int(val)
275
276 # *THE* mighty config element class
277 #
278 # allows you to store/edit a sequence of values.
279 # can be used for IP-addresses, dates, plain integers, ...
280 # several helper exist to ease this up a bit.
281 #
282 class ConfigSequence(ConfigElement):
283         def __init__(self, seperator, limits, censor_char = "", default = None):
284                 ConfigElement.__init__(self)
285                 assert isinstance(limits, list) and len(limits[0]) == 2, "limits must be [(min, max),...]-tuple-list"
286                 assert censor_char == "" or len(censor_char) == 1, "censor char must be a single char (or \"\")"
287                 #assert isinstance(default, list), "default must be a list"
288                 #assert isinstance(default[0], int), "list must contain numbers"
289                 #assert len(default) == len(limits), "length must match"
290
291                 self.marked_pos = 0
292                 self.seperator = seperator
293                 self.limits = limits
294                 self.censor_char = censor_char
295                 
296                 self.default = default
297                 self.value = copy.copy(default)
298
299         def validate(self):
300                 max_pos = 0
301                 num = 0
302                 for i in self._value:
303                         max_pos += len(str(self.limits[num][1]))
304
305                         while self._value[num] < self.limits[num][0]:
306                                 self.value[num] += 1
307
308                         while self._value[num] > self.limits[num][1]:
309                                 self._value[num] -= 1
310
311                         num += 1
312
313                 if self.marked_pos >= max_pos:
314                         self.marked_pos = max_pos - 1
315
316                 if self.marked_pos < 0:
317                         self.marked_pos = 0
318
319         def validatePos(self):
320                 if self.marked_pos < 0:
321                         self.marked_pos = 0
322                         
323                 total_len = sum([len(str(x[1])) for x in self.limits])
324
325                 if self.marked_pos >= total_len:
326                         self.marked_pos = total_len - 1
327
328         def handleKey(self, key):
329                 if key == KEY_LEFT:
330                         self.marked_pos -= 1
331                         self.validatePos()
332
333                 if key == KEY_RIGHT:
334                         self.marked_pos += 1
335                         self.validatePos()
336                 
337                 if key in KEY_NUMBERS:
338                         block_len = []
339                         for x in self.limits:
340                                 block_len.append(len(str(x[1])))
341                         
342                         total_len = sum(block_len)
343
344                         pos = 0
345                         blocknumber = 0
346                         block_len_total = [0, ]
347                         for x in block_len:
348                                 pos += block_len[blocknumber]
349                                 block_len_total.append(pos)
350                                 if pos - 1 >= self.marked_pos:
351                                         pass
352                                 else:
353                                         blocknumber += 1
354
355                         number = getKeyNumber(key)
356                         
357                         # length of numberblock
358                         number_len = len(str(self.limits[blocknumber][1]))
359
360                         # position in the block
361                         posinblock = self.marked_pos - block_len_total[blocknumber]
362                         
363                         oldvalue = self._value[blocknumber]
364                         olddec = oldvalue % 10 ** (number_len - posinblock) - (oldvalue % 10 ** (number_len - posinblock - 1))
365                         newvalue = oldvalue - olddec + (10 ** (number_len - posinblock - 1) * number)
366                         
367                         self._value[blocknumber] = newvalue
368                         self.marked_pos += 1
369                 
370                         self.validate()
371                         self.changed()
372                         
373         def getMulti(self, selected):
374                 value = ""
375                 mPos = self.marked_pos
376                 num = 0;
377                 for i in self._value:
378                         if len(value):  #fixme no heading separator possible
379                                 value += self.seperator
380                                 if mPos >= len(value) - 1:
381                                         mPos += 1
382
383                         if self.censor_char == "":
384                                 value += ("%0" + str(len(str(self.limits[num][1]))) + "d") % i
385                         else:
386                                 value += (self.censor_char * len(str(self.limits[num][1])))
387                         num += 1
388
389                         # only mark cursor when we are selected
390                         # (this code is heavily ink optimized!)
391                 if self.enabled:
392                         return ("mtext"[1-selected:], value, [mPos])
393                 else:
394                         return ("text", value)
395
396         def tostring(self, val):
397                 return self.seperator.join([self.saveSingle(x) for x in val])
398         
399         def saveSingle(self, v):
400                 return str(v)
401
402         def fromstring(self, value):
403                 return [int(x) for x in self.saved_value.split(self.seperator)]
404
405 class ConfigIP(ConfigSequence):
406         def __init__(self, default):
407                 ConfigSequence.__init__(self, seperator = ".", limits = [(0,255),(0,255),(0,255),(0,255)], default = default)
408
409 class ConfigMAC(ConfigSequence):
410         def __init__(self, default):
411                 ConfigSequence.__init__(self, seperator = ":", limits = [(1,255),(1,255),(1,255),(1,255),(1,255),(1,255)], default = default)
412
413 class ConfigPosition(ConfigSequence):
414         def __init__(self, default, args):
415                 ConfigSequence.__init__(self, seperator = ",", limits = [(0,args[0]),(0,args[1]),(0,args[2]),(0,args[3])], default = default)
416
417 class ConfigClock(ConfigSequence):
418         def __init__(self, default):
419                 import time
420                 t = time.localtime(default)
421                 ConfigSequence.__init__(self, seperator = ":", limits = [(0,23),(0,59)], default = [t.tm_hour, t.tm_min])
422
423 class ConfigInteger(ConfigSequence):
424         def __init__(self, default, limits):
425                 ConfigSequence.__init__(self, seperator = ":", limits = [limits], default = default)
426         
427         # you need to override this to do input validation
428         def setValue(self, value):
429                 self._value = [value]
430                 self.changed()
431
432         def getValue(self):
433                 return self._value[0]
434         
435         value = property(getValue, setValue)
436
437         def fromstring(self, value):
438                 return int(value)
439
440         def tostring(self, value):
441                 return str(value)
442
443 class ConfigPIN(ConfigInteger):
444         def __init__(self, default, len = 4, censor = ""):
445                 assert isinstance(default, int), "ConfigPIN default must be an integer"
446                 if default == -1:
447                         default = "aaaa"
448                 ConfigSequence.__init__(self, seperator = ":", limits = [(0, (10**len)-1)], censor_char = censor, default = default)
449                 self.len = len
450
451         def getLength(self):
452                 return self.len
453
454 class ConfigFloat(ConfigSequence):
455         def __init__(self, default, limits):
456                 ConfigSequence.__init__(self, seperator = ".", limits = limits, default = default)
457
458         def getFloat(self):
459                 return float(self.value[1] / float(self.limits[1][1] + 1) + self.value[0])
460
461         float = property(getFloat)
462
463 # an editable text...
464 class ConfigText(ConfigElement, NumericalTextInput):
465         def __init__(self, default = "", fixed_size = True):
466                 ConfigElement.__init__(self)
467                 NumericalTextInput.__init__(self, nextFunc = self.nextFunc, handleTimeout = False)
468                 
469                 self.marked_pos = 0
470                 self.fixed_size = fixed_size
471
472                 self.value = self.default = default
473
474         def validateMarker(self):
475                 if self.marked_pos < 0:
476                         self.marked_pos = 0
477                 if self.marked_pos >= len(self.text):
478                         self.marked_pos = len(self.text) - 1
479
480         #def nextEntry(self):
481         #       self.vals[1](self.getConfigPath())
482
483         def handleKey(self, key):
484                 # this will no change anything on the value itself
485                 # so we can handle it here in gui element
486                 if key == KEY_DELETE:
487                         self.text = self.text[0:self.marked_pos] + self.text[self.marked_pos + 1:]
488                 elif key == KEY_LEFT:
489                         self.marked_pos -= 1
490                 elif key == KEY_RIGHT:
491                         self.marked_pos += 1
492                         if not self.fixed_size:
493                                 if self.marked_pos >= len(self.text):
494                                         self.text = self.text.ljust(len(self.text) + 1)
495                 elif key in KEY_NUMBERS:
496                         number = self.getKey(getKeyNumber(key))
497                         self.text = self.text[0:self.marked_pos] + unicode(number) + self.text[self.marked_pos + 1:]
498                 elif key == KEY_TIMEOUT:
499                         self.timeout()
500                         return
501
502                 self.validateMarker()
503                 self.changed()
504
505         def nextFunc(self):
506                 self.marked_pos += 1
507                 self.validateMarker()
508                 self.changed()
509
510         def getValue(self):
511                 return self.text.encode("utf-8")
512                 
513         def setValue(self, val):
514                 try:
515                         self.text = val.decode("utf-8")
516                 except UnicodeDecodeError:
517                         self.text = val
518                         print "Broken UTF8!"
519
520         value = property(getValue, setValue)
521         _value = property(getValue, setValue)
522
523         def getMulti(self, selected):
524                 return ("mtext"[1-selected:], self.value, [self.marked_pos])
525
526         def helpWindow(self):
527                 from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
528                 return (NumericalTextInputHelpDialog,self)
529
530         def getHTML(self, id):
531                 return '<input type="text" name="' + id + '" value="' + self.value + '" /><br>\n'
532
533         def unsafeAssign(self, value):
534                 self.value = str(value)
535
536 # a slider.
537 class ConfigSlider(ConfigElement):
538         def __init__(self, default = 0, increment = 1, limits = (0, 100)):
539                 ConfigElement.__init__(self)
540                 self.value = self.default = default
541                 self.min = limits[0]
542                 self.max = limits[1]
543                 self.increment = increment
544
545         def checkValues(self):
546                 if self.value < self.min:
547                         self.value = self.min
548
549                 if self.value > self.max:
550                         self.value = self.max
551
552         def handleKey(self, key):
553                 if key == KEY_LEFT:
554                         self.value -= self.increment
555                 elif key == KEY_RIGHT:
556                         self.value += self.increment
557                 else:
558                         return
559
560                 self.checkValues()
561                 self.changed()
562
563         def getMulti(self, selected):
564                 self.checkValues()
565                 return ("slider", self.value, self.max)
566
567         def fromstring(self, value):
568                 return int(value)
569
570 # a satlist. in fact, it's a ConfigSelection.
571 class ConfigSatlist(ConfigSelection):
572         def __init__(self, list, default = None):
573                 if default is not None:
574                         default = str(default)
575                 ConfigSelection.__init__(self, choices = [(str(orbpos), desc) for (orbpos, desc) in list], default = default)
576
577         def getOrbitalPosition(self):
578                 if self.value == "":
579                         return None
580                 return int(self.value)
581         
582         orbital_position = property(getOrbitalPosition)
583
584 # nothing.
585 class ConfigNothing(ConfigSelection):
586         def __init__(self):
587                 ConfigSelection.__init__(self, choices = [""])
588
589 # until here, 'saved_value' always had to be a *string*.
590 # now, in ConfigSubsection, and only there, saved_value
591 # is a dict, essentially forming a tree.
592 #
593 # config.foo.bar=True
594 # config.foobar=False
595 #
596 # turns into:
597 # config.saved_value == {"foo": {"bar": "True"}, "foobar": "False"}
598 #
599
600
601 class ConfigSubsectionContent(object):
602         pass
603
604 # we store a backup of the loaded configuration
605 # data in self.stored_values, to be able to deploy
606 # them when a new config element will be added,
607 # so non-default values are instantly available
608
609 # A list, for example:
610 # config.dipswitches = ConfigSubList()
611 # config.dipswitches.append(ConfigYesNo())
612 # config.dipswitches.append(ConfigYesNo())
613 # config.dipswitches.append(ConfigYesNo())
614 class ConfigSubList(list, object):
615         def __init__(self):
616                 object.__init__(self)
617                 list.__init__(self)
618                 self.stored_values = {}
619
620         def save(self):
621                 for x in self:
622                         x.save()
623         
624         def load(self):
625                 for x in self:
626                         x.load()
627
628         def getSavedValue(self):
629                 res = {}
630                 for i in range(len(self)):
631                         sv = self[i].saved_value
632                         if sv is not None:
633                                 res[str(i)] = sv
634                 return res
635
636         def setSavedValue(self, values):
637                 self.stored_values = dict(values)
638                 for (key, val) in self.stored_values.items():
639                         if int(key) < len(self):
640                                 self[int(key)].saved_value = val
641
642         saved_value = property(getSavedValue, setSavedValue)
643         
644         def append(self, item):
645                 i = str(len(self))
646                 list.append(self, item)
647                 if i in self.stored_values:
648                         item.saved_value = self.stored_values[i]
649                         item.load()
650
651 # same as ConfigSubList, just as a dictionary.
652 # care must be taken that the 'key' has a proper
653 # str() method, because it will be used in the config
654 # file.
655 class ConfigSubDict(dict, object):
656         def __init__(self):
657                 object.__init__(self)
658                 dict.__init__(self)
659                 self.stored_values = {}
660
661         def save(self):
662                 for x in self.values():
663                         x.save()
664         
665         def load(self):
666                 for x in self.values():
667                         x.load()
668
669         def getSavedValue(self):
670                 res = {}
671                 for (key, val) in self.items():
672                         if val.saved_value is not None:
673                                 res[str(key)] = val.saved_value
674                 return res
675
676         def setSavedValue(self, values):
677                 self.stored_values = dict(values)
678                 for (key, val) in self.items():
679                         if str(key) in self.stored_values:
680                                 val = self.stored_values[str(key)]
681
682         saved_value = property(getSavedValue, setSavedValue)
683
684         def __setitem__(self, key, item):
685                 dict.__setitem__(self, key, item)
686                 if str(key) in self.stored_values:
687                         item.saved_value = self.stored_values[str(key)]
688                         item.load()
689
690 # Like the classes above, just with a more "native"
691 # syntax.
692 #
693 # some evil stuff must be done to allow instant
694 # loading of added elements. this is why this class
695 # is so complex.
696 #
697 # we need the 'content' because we overwrite 
698 # __setattr__.
699 # If you don't understand this, try adding
700 # __setattr__ to a usual exisiting class and you will.
701 class ConfigSubsection(object):
702         def __init__(self):
703                 object.__init__(self)
704                 self.__dict__["content"] = ConfigSubsectionContent()
705                 self.content.items = { }
706                 self.content.stored_values = { }
707         
708         def __setattr__(self, name, value):
709                 if name == "saved_value":
710                         return self.setSavedValue(value)
711                 assert isinstance(value, ConfigSubsection) or isinstance(value, ConfigElement) or isinstance(value, ConfigSubList) or isinstance(value, ConfigSubDict), "ConfigSubsections can only store ConfigSubsections, ConfigSubLists, ConfigSubDicts or ConfigElements"
712                 self.content.items[name] = value
713                 if name in self.content.stored_values:
714                         #print "ok, now we have a new item,", name, "and have the following value for it:", self.content.stored_values[name]
715                         value.saved_value = self.content.stored_values[name]
716                         value.load()
717
718         def __getattr__(self, name):
719                 return self.content.items[name]
720
721         def getSavedValue(self):
722                 res = self.content.stored_values
723                 for (key, val) in self.content.items.items():
724                         if val.saved_value is not None:
725                                 res[key] = val.saved_value
726                         elif key in res:
727                                 del res[key]
728                                 
729                 return res
730
731         def setSavedValue(self, values):
732                 values = dict(values)
733                 
734                 self.content.stored_values = values
735                 
736                 for (key, val) in self.content.items.items():
737                         if key in values:
738                                 val.setSavedValue(values[key])
739
740         saved_value = property(getSavedValue, setSavedValue)
741
742         def save(self):
743                 for x in self.content.items.values():
744                         x.save()
745
746         def load(self):
747                 for x in self.content.items.values():
748                         x.load()
749
750 # the root config object, which also can "pickle" (=serialize)
751 # down the whole config tree.
752 #
753 # we try to keep non-existing config entries, to apply them whenever
754 # a new config entry is added to a subsection
755 # also, non-existing config entries will be saved, so they won't be
756 # lost when a config entry disappears.
757 class Config(ConfigSubsection):
758         def __init__(self):
759                 ConfigSubsection.__init__(self)
760
761         def pickle_this(self, prefix, topickle, result):
762                 for (key, val) in topickle.items():
763                         name = prefix + "." + key
764                         
765                         if isinstance(val, dict):
766                                 self.pickle_this(name, val, result)
767                         elif isinstance(val, tuple):
768                                 result.append(name + "=" + val[0]) # + " ; " + val[1])
769                         else:
770                                 result.append(name + "=" + val)
771
772         def pickle(self):
773                 result = [ ]
774                 self.pickle_this("config", self.saved_value, result)
775                 return '\n'.join(result) + "\n"
776
777         def unpickle(self, lines):
778                 tree = { }
779                 for l in lines:
780                         if not len(l) or l[0] == '#':
781                                 continue
782                         
783                         n = l.find('=')
784                         val = l[n+1:].strip()
785
786                         names = l[:n].split('.')
787 #                       if val.find(' ') != -1:
788 #                               val = val[:val.find(' ')]
789
790                         base = tree
791                         
792                         for n in names[:-1]:
793                                 base = base.setdefault(n, {})
794                         
795                         base[names[-1]] = val
796
797                 # we inherit from ConfigSubsection, so ...
798                 #object.__setattr__(self, "saved_value", tree["config"])
799                 self.setSavedValue(tree["config"])
800
801         def saveToFile(self, filename):
802                 f = open(filename, "w")
803                 f.write(self.pickle())
804                 f.close()
805
806         def loadFromFile(self, filename):
807                 f = open(filename, "r")
808                 self.unpickle(f.readlines())
809                 f.close()
810
811 config = Config()
812 config.misc = ConfigSubsection()
813
814 class ConfigFile:
815         CONFIG_FILE = resolveFilename(SCOPE_CONFIG, "config2")
816
817         def load(self):
818                 try:
819                         config.loadFromFile(self.CONFIG_FILE)
820                 except IOError, e:
821                         print "unable to load config (%s), assuming defaults..." % str(e)
822         
823         def save(self):
824                 config.save()
825                 config.saveToFile(self.CONFIG_FILE)
826         
827         def getResolvedKey(self, key):
828                 return None # FIXME
829
830 def NoSave(element):
831         element.disableSave()
832         return element
833
834 configfile = ConfigFile()
835
836 configfile.load()
837
838 def getConfigListEntry(desc, config):
839         return (desc, config)
840
841 #def _(x):
842 #       return x
843 #
844 #config.bla = ConfigSubsection()
845 #config.bla.test = ConfigYesNo()
846 #config.nim = ConfigSubList()
847 #config.nim.append(ConfigSubsection())
848 #config.nim[0].bla = ConfigYesNo()
849 #config.nim.append(ConfigSubsection())
850 #config.nim[1].bla = ConfigYesNo()
851 #config.nim[1].blub = ConfigYesNo()
852 #config.arg = ConfigSubDict()
853 #config.arg["Hello"] = ConfigYesNo()
854 #
855 #config.arg["Hello"].handleKey(KEY_RIGHT)
856 #config.arg["Hello"].handleKey(KEY_RIGHT)
857 #
858 ##config.saved_value
859 #
860 ##configfile.save()
861 #config.save()
862 #print config.pickle()