small speedups
[enigma2.git] / lib / python / Components / config.py
1 from enigma import getPrevAsciiCode
2 from Tools.NumericalTextInput import NumericalTextInput
3 from Tools.Directories import resolveFilename, SCOPE_CONFIG
4 from Components.Harddisk import harddiskmanager
5 from copy import copy as copy_copy
6 from os import path as os_path
7 from time import localtime, strftime
8
9 # ConfigElement, the base class of all ConfigElements.
10
11 # it stores:
12 #   value    the current value, usefully encoded.
13 #            usually a property which retrieves _value,
14 #            and maybe does some reformatting
15 #   _value   the value as it's going to be saved in the configfile,
16 #            though still in non-string form.
17 #            this is the object which is actually worked on.
18 #   default  the initial value. If _value is equal to default,
19 #            it will not be stored in the config file
20 #   saved_value is a text representation of _value, stored in the config file
21 #
22 # and has (at least) the following methods:
23 #   save()   stores _value into saved_value, 
24 #            (or stores 'None' if it should not be stored)
25 #   load()   loads _value from saved_value, or loads
26 #            the default if saved_value is 'None' (default)
27 #            or invalid.
28 #
29 class ConfigElement(object):
30         def __init__(self):
31                 object.__init__(self)
32                 self.saved_value = None
33                 self.last_value = None
34                 self.save_disabled = False
35                 self.notifiers = []
36                 self.notifiers_final = []
37                 self.enabled = True
38                 self.callNotifiersOnSaveAndCancel = False
39
40         # you need to override this to do input validation
41         def setValue(self, value):
42                 self._value = value
43                 self.changed()
44
45         def getValue(self):
46                 return self._value
47         
48         value = property(getValue, setValue)
49
50         # you need to override this if self.value is not a string
51         def fromstring(self, value):
52                 return value
53
54         # you can overide this for fancy default handling
55         def load(self):
56                 sv = self.saved_value
57                 if sv is None:
58                         self.value = self.default
59                 else:
60                         self.value = self.fromstring(sv)
61
62         def tostring(self, value):
63                 return str(value)
64
65         # you need to override this if str(self.value) doesn't work
66         def save(self):
67                 if self.save_disabled or self.value == self.default:
68                         self.saved_value = None
69                 else:
70                         self.saved_value = self.tostring(self.value)
71                 if self.callNotifiersOnSaveAndCancel:
72                         self.changed()
73
74         def cancel(self):
75                 self.load()
76                 if self.callNotifiersOnSaveAndCancel:
77                         self.changed()
78
79         def isChanged(self):
80                 sv = self.saved_value
81                 if sv is None and self.value == self.default:
82                         return False
83                 return self.tostring(self.value) != sv
84
85         def changed(self):
86                 for x in self.notifiers:
87                         x(self)
88                         
89         def changedFinal(self):
90                 for x in self.notifiers_final:
91                         x(self)
92                         
93         def addNotifier(self, notifier, initial_call = True, immediate_feedback = True):
94                 assert callable(notifier), "notifiers must be callable"
95                 if immediate_feedback:
96                         self.notifiers.append(notifier)
97                 else:
98                         self.notifiers_final.append(notifier)
99                 # CHECKME:
100                 # do we want to call the notifier
101                 #  - at all when adding it? (yes, though optional)
102                 #  - when the default is active? (yes)
103                 #  - when no value *yet* has been set,
104                 #    because no config has ever been read (currently yes)
105                 #    (though that's not so easy to detect.
106                 #     the entry could just be new.)
107                 if initial_call:
108                         notifier(self)
109
110         def disableSave(self):
111                 self.save_disabled = True
112
113         def __call__(self, selected):
114                 return self.getMulti(selected)
115
116         def onSelect(self, session):
117                 pass
118
119         def onDeselect(self, session):
120                 if not self.last_value == self.value:
121                         self.changedFinal()
122                         self.last_value = self.value
123
124 KEY_LEFT = 0
125 KEY_RIGHT = 1
126 KEY_OK = 2
127 KEY_DELETE = 3
128 KEY_BACKSPACE = 4
129 KEY_HOME = 5
130 KEY_END = 6
131 KEY_TOGGLEOW = 7
132 KEY_ASCII = 8
133 KEY_TIMEOUT = 9
134 KEY_NUMBERS = range(12, 12+10)
135 KEY_0 = 12
136 KEY_9 = 12+9
137
138 def getKeyNumber(key):
139         assert key in KEY_NUMBERS
140         return key - KEY_0
141
142 class choicesList(object): # XXX: we might want a better name for this
143         LIST_TYPE_LIST = 1
144         LIST_TYPE_DICT = 2
145
146         def __init__(self, choices, type = None):
147                 object.__init__(self)
148                 self.choices = choices
149                 if type is None:
150                         if isinstance(choices, list):
151                                 self.type = choicesList.LIST_TYPE_LIST
152                         elif isinstance(choices, dict):
153                                 self.type = choicesList.LIST_TYPE_DICT
154                         else:
155                                 assert False, "choices must be dict or list!"
156                 else:
157                         self.type = type
158
159         def __list__(self):
160                 if self.type == choicesList.LIST_TYPE_LIST:
161                         ret = [isinstance(x, tuple) and x[0] or x for x in self.choices]
162                 else:
163                         ret = self.choices.keys()
164                 return ret or [""]
165
166         def __iter__(self):
167                 if self.type == choicesList.LIST_TYPE_LIST:
168                         ret = [isinstance(x, tuple) and x[0] or x for x in self.choices]
169                 else:
170                         ret = self.choices
171                 return iter(ret or [""])
172
173         def __len__(self):
174                 return len(self.choices) or 1
175
176         def __getitem__(self, index):
177                 if self.type == choicesList.LIST_TYPE_LIST:
178                         ret = self.choices[index]
179                         if isinstance(ret, tuple):
180                                 ret = ret[0]
181                         return ret
182                 return self.choices.keys()[index]
183
184         def index(self, value):
185                 return self.__list__().index(value)
186
187         def __setitem__(self, index, value):
188                 if self.type == choicesList.LIST_TYPE_LIST:
189                         orig = self.choices[index]
190                         if isinstance(orig, tuple):
191                                 self.choices[index] = (value, orig[1])
192                         else:
193                                 self.choices[index] = value
194                 else:
195                         key = self.choices.keys()[index]
196                         orig = self.choices[key]
197                         del self.choices[key]
198                         self.choices[value] = orig
199
200         def default(self):
201                 choices = self.choices
202                 if not choices:
203                         return ""
204                 if self.type is choicesList.LIST_TYPE_LIST:
205                         default = choices[0]
206                         if isinstance(default, tuple):
207                                 default = default[0]
208                 else:
209                         default = choices.keys()[0]
210                 return default
211
212 class descriptionList(choicesList): # XXX: we might want a better name for this
213         def __list__(self):
214                 if self.type == choicesList.LIST_TYPE_LIST:
215                         ret = [isinstance(x, tuple) and x[1] or x for x in self.choices]
216                 else:
217                         ret = self.choices.values()
218                 return ret or [""]
219
220         def __iter__(self):
221                 return iter(self.__list__())
222
223         def __getitem__(self, index):
224                 if self.type == choicesList.LIST_TYPE_LIST:
225                         for x in self.choices:
226                                 if isinstance(x, tuple):
227                                         if x[0] == index:
228                                                 return str(x[1])
229                                 elif x == index:
230                                         return str(x)
231                         return str(index) # Fallback!
232                 else:
233                         return str(self.choices.get(index, ""))
234
235         def __setitem__(self, index, value):
236                 if self.type == choicesList.LIST_TYPE_LIST:
237                         i = self.index(index)
238                         orig = self.choices[i]
239                         if isinstance(orig, tuple):
240                                 self.choices[i] = (orig[0], value)
241                         else:
242                                 self.choices[i] = value
243                 else:
244                         self.choices[index] = value
245
246 #
247 # ConfigSelection is a "one of.."-type.
248 # it has the "choices", usually a list, which contains
249 # (id, desc)-tuples (or just only the ids, in case the id
250 # will be used as description)
251 #
252 # all ids MUST be plain strings.
253 #
254 class ConfigSelection(ConfigElement):
255         def __init__(self, choices, default = None):
256                 ConfigElement.__init__(self)
257                 self.choices = choicesList(choices)
258
259                 if default is None:
260                         default = self.choices.default()
261
262                 self.default = self._value = self.last_value = default
263                 self.changed()
264
265         def setChoices(self, choices, default = None):
266                 self.choices = choicesList(choices)
267
268                 if default is None:
269                         default = self.choices.default()
270
271                 if self.value not in self.choices:
272                         self.value = default
273
274         def setValue(self, value):
275                 if value in self.choices:
276                         self._value = value
277                 else:
278                         self._value = self.default
279                 self.changed()
280
281         def tostring(self, val):
282                 return val
283
284         def getValue(self):
285                 return self._value
286
287         def setCurrentText(self, text):
288                 i = self.choices.index(self.value)
289                 self.choices[i] = text
290                 self.description[text] = text
291                 self._value = text
292
293         value = property(getValue, setValue)
294         
295         def getIndex(self):
296                 return self.choices.index(self.value)
297         
298         index = property(getIndex)
299
300         # GUI
301         def handleKey(self, key):
302                 nchoices = len(self.choices)
303                 i = self.choices.index(self.value)
304                 if key == KEY_LEFT:
305                         self.value = self.choices[(i + nchoices - 1) % nchoices]
306                 elif key == KEY_RIGHT:
307                         self.value = self.choices[(i + 1) % nchoices]
308                 elif key == KEY_HOME:
309                         self.value = self.choices[0]
310                 elif key == KEY_END:
311                         self.value = self.choices[nchoices - 1]
312
313         def selectNext(self):
314                 nchoices = len(self.choices)
315                 i = self.choices.index(self.value)
316                 self.value = self.choices[(i + 1) % nchoices]
317
318         def getText(self):
319                 descr = self.description[self.value]
320                 if descr:
321                         return _(descr)
322                 return descr
323
324         def getMulti(self, selected):
325                 descr = self.description[self.value]
326                 if descr:
327                         return ("text", _(descr))
328                 return ("text", descr)
329
330         # HTML
331         def getHTML(self, id):
332                 res = ""
333                 for v in self.choices:
334                         descr = self.description[v]
335                         if self.value == v:
336                                 checked = 'checked="checked" '
337                         else:
338                                 checked = ''
339                         res += '<input type="radio" name="' + id + '" ' + checked + 'value="' + v + '">' + descr + "</input></br>\n"
340                 return res;
341
342         def unsafeAssign(self, value):
343                 # setValue does check if value is in choices. This is safe enough.
344                 self.value = value
345
346         description = property(lambda self: descriptionList(self.choices.choices, self.choices.type))
347
348 # a binary decision.
349 #
350 # several customized versions exist for different
351 # descriptions.
352 #
353 boolean_descriptions = {False: "false", True: "true"}
354 class ConfigBoolean(ConfigElement):
355         def __init__(self, default = False, descriptions = boolean_descriptions):
356                 ConfigElement.__init__(self)
357                 self.descriptions = descriptions
358                 self.value = self.last_value = self.default = default
359
360         def handleKey(self, key):
361                 if key in [KEY_LEFT, KEY_RIGHT]:
362                         self.value = not self.value
363                 elif key == KEY_HOME:
364                         self.value = False
365                 elif key == KEY_END:
366                         self.value = True
367
368         def getText(self):
369                 descr = self.descriptions[self.value]
370                 if descr:
371                         return _(descr)
372                 return descr
373
374         def getMulti(self, selected):
375                 descr = self.descriptions[self.value]
376                 if descr:
377                         return ("text", _(descr))
378                 return ("text", descr)
379
380         def tostring(self, value):
381                 if not value:
382                         return "false"
383                 else:
384                         return "true"
385
386         def fromstring(self, val):
387                 if val == "true":
388                         return True
389                 else:
390                         return False
391
392         def getHTML(self, id):
393                 if self.value:
394                         checked = ' checked="checked"'
395                 else:
396                         checked = ''
397                 return '<input type="checkbox" name="' + id + '" value="1" ' + checked + " />"
398
399         # this is FLAWED. and must be fixed.
400         def unsafeAssign(self, value):
401                 if value == "1":
402                         self.value = True
403                 else:
404                         self.value = False
405
406         def onDeselect(self, session):
407                 if not self.last_value == self.value:
408                         self.changedFinal()
409                         self.last_value = self.value
410
411 yes_no_descriptions = {False: _("no"), True: _("yes")}
412 class ConfigYesNo(ConfigBoolean):
413         def __init__(self, default = False):
414                 ConfigBoolean.__init__(self, default = default, descriptions = yes_no_descriptions)
415
416 on_off_descriptions = {False: _("off"), True: _("on")}
417 class ConfigOnOff(ConfigBoolean):
418         def __init__(self, default = False):
419                 ConfigBoolean.__init__(self, default = default, descriptions = on_off_descriptions)
420
421 enable_disable_descriptions = {False: _("disable"), True: _("enable")}
422 class ConfigEnableDisable(ConfigBoolean):
423         def __init__(self, default = False):
424                 ConfigBoolean.__init__(self, default = default, descriptions = enable_disable_descriptions)
425
426 class ConfigDateTime(ConfigElement):
427         def __init__(self, default, formatstring, increment = 86400):
428                 ConfigElement.__init__(self)
429                 self.increment = increment
430                 self.formatstring = formatstring
431                 self.value = self.last_value = self.default = int(default)
432
433         def handleKey(self, key):
434                 if key == KEY_LEFT:
435                         self.value = self.value - self.increment
436                 elif key == KEY_RIGHT:
437                         self.value = self.value + self.increment
438                 elif key == KEY_HOME or key == KEY_END:
439                         self.value = self.default
440
441         def getText(self):
442                 return strftime(self.formatstring, localtime(self.value))
443
444         def getMulti(self, selected):
445                 return ("text", strftime(self.formatstring, localtime(self.value)))
446
447         def fromstring(self, val):
448                 return int(val)
449
450 # *THE* mighty config element class
451 #
452 # allows you to store/edit a sequence of values.
453 # can be used for IP-addresses, dates, plain integers, ...
454 # several helper exist to ease this up a bit.
455 #
456 class ConfigSequence(ConfigElement):
457         def __init__(self, seperator, limits, default, censor_char = ""):
458                 ConfigElement.__init__(self)
459                 assert isinstance(limits, list) and len(limits[0]) == 2, "limits must be [(min, max),...]-tuple-list"
460                 assert censor_char == "" or len(censor_char) == 1, "censor char must be a single char (or \"\")"
461                 #assert isinstance(default, list), "default must be a list"
462                 #assert isinstance(default[0], int), "list must contain numbers"
463                 #assert len(default) == len(limits), "length must match"
464
465                 self.marked_pos = 0
466                 self.seperator = seperator
467                 self.limits = limits
468                 self.censor_char = censor_char
469
470                 self.last_value = self.default = default
471                 self.value = copy_copy(default)
472                 self.endNotifier = None
473
474         def validate(self):
475                 max_pos = 0
476                 num = 0
477                 for i in self._value:
478                         max_pos += len(str(self.limits[num][1]))
479
480                         if self._value[num] < self.limits[num][0]:
481                                 self._value[num] = self.limits[num][0]
482
483                         if self._value[num] > self.limits[num][1]:
484                                 self._value[num] = self.limits[num][1]
485
486                         num += 1
487
488                 if self.marked_pos >= max_pos:
489                         if endNotifier:
490                                 for x in self.endNotifier:
491                                         x(self)
492                         self.marked_pos = max_pos - 1
493
494                 if self.marked_pos < 0:
495                         self.marked_pos = 0
496
497         def validatePos(self):
498                 if self.marked_pos < 0:
499                         self.marked_pos = 0
500
501                 total_len = sum([len(str(x[1])) for x in self.limits])
502
503                 if self.marked_pos >= total_len:
504                         self.marked_pos = total_len - 1
505
506         def addEndNotifier(self, notifier):
507                 if endNotifier is None:
508                         endNotifier = []
509                 self.endNotifier.append(notifier)
510
511         def handleKey(self, key):
512                 if key == KEY_LEFT:
513                         self.marked_pos -= 1
514                         self.validatePos()
515
516                 if key == KEY_RIGHT:
517                         self.marked_pos += 1
518                         self.validatePos()
519
520                 if key == KEY_HOME:
521                         self.marked_pos = 0
522                         self.validatePos()
523
524                 if key == KEY_END:
525                         max_pos = 0
526                         num = 0
527                         for i in self._value:
528                                 max_pos += len(str(self.limits[num][1]))
529                                 num += 1
530                         self.marked_pos = max_pos - 1
531                         self.validatePos()
532
533                 if key in KEY_NUMBERS or key == KEY_ASCII:
534                         if key == KEY_ASCII:
535                                 code = getPrevAsciiCode()
536                                 if code < 48 or code > 57:
537                                         return
538                                 number = code - 48
539                         else:
540                                 number = getKeyNumber(key)
541
542                         block_len = []
543                         for x in self.limits:
544                                 block_len.append(len(str(x[1])))
545
546                         total_len = sum(block_len)
547
548                         pos = 0
549                         blocknumber = 0
550                         block_len_total = [0, ]
551                         for x in block_len:
552                                 pos += block_len[blocknumber]
553                                 block_len_total.append(pos)
554                                 if pos - 1 >= self.marked_pos:
555                                         pass
556                                 else:
557                                         blocknumber += 1
558
559                         # length of numberblock
560                         number_len = len(str(self.limits[blocknumber][1]))
561
562                         # position in the block
563                         posinblock = self.marked_pos - block_len_total[blocknumber]
564
565                         oldvalue = self._value[blocknumber]
566                         olddec = oldvalue % 10 ** (number_len - posinblock) - (oldvalue % 10 ** (number_len - posinblock - 1))
567                         newvalue = oldvalue - olddec + (10 ** (number_len - posinblock - 1) * number)
568
569                         self._value[blocknumber] = newvalue
570                         self.marked_pos += 1
571
572                         self.validate()
573                         self.changed()
574
575         def genText(self):
576                 value = ""
577                 mPos = self.marked_pos
578                 num = 0;
579                 for i in self._value:
580                         if value:       #fixme no heading separator possible
581                                 value += self.seperator
582                                 if mPos >= len(value) - 1:
583                                         mPos += 1
584                         if self.censor_char == "":
585                                 value += ("%0" + str(len(str(self.limits[num][1]))) + "d") % i
586                         else:
587                                 value += (self.censor_char * len(str(self.limits[num][1])))
588                         num += 1
589                 return (value, mPos)
590
591         def getText(self):
592                 (value, mPos) = self.genText()
593                 return value
594
595         def getMulti(self, selected):
596                 (value, mPos) = self.genText()
597                         # only mark cursor when we are selected
598                         # (this code is heavily ink optimized!)
599                 if self.enabled:
600                         return ("mtext"[1-selected:], value, [mPos])
601                 else:
602                         return ("text", value)
603
604         def tostring(self, val):
605                 return self.seperator.join([self.saveSingle(x) for x in val])
606
607         def saveSingle(self, v):
608                 return str(v)
609
610         def fromstring(self, value):
611                 return [int(x) for x in value.split(self.seperator)]
612
613         def onDeselect(self, session):
614                 if self.last_value != self._value:
615                         self.changedFinal()
616                         self.last_value = copy_copy(self._value)
617
618 ip_limits = [(0,255),(0,255),(0,255),(0,255)]
619 class ConfigIP(ConfigSequence):
620         def __init__(self, default, auto_jump = False):
621                 ConfigSequence.__init__(self, seperator = ".", limits = ip_limits, default = default)
622                 self.block_len = []
623                 for x in self.limits:
624                         self.block_len.append(len(str(x[1])))
625                 self.marked_block = 0
626                 self.overwrite = True
627                 self.auto_jump = auto_jump
628
629         def handleKey(self, key):
630                 
631                 if key == KEY_LEFT:
632                         if self.marked_block > 0:
633                                 self.marked_block -= 1
634                         self.overwrite = True
635
636                 if key == KEY_RIGHT:
637                         if self.marked_block < len(self.limits)-1:
638                                 self.marked_block += 1
639                         self.overwrite = True
640
641                 if key == KEY_HOME:
642                         self.marked_block = 0
643                         self.overwrite = True
644
645                 if key == KEY_END:
646                         self.marked_block = len(self.limits)-1
647                         self.overwrite = True
648
649                 if key in KEY_NUMBERS or key == KEY_ASCII:
650                         if key == KEY_ASCII:
651                                 code = getPrevAsciiCode()
652                                 if code < 48 or code > 57:
653                                         return
654                                 number = code - 48
655                         else:   
656                                 number = getKeyNumber(key)
657                         oldvalue = self._value[self.marked_block]
658                         
659                         if self.overwrite:
660                                 self._value[self.marked_block] = number
661                                 self.overwrite = False          
662                         else:
663                                 oldvalue *= 10
664                                 newvalue = oldvalue + number
665                                 if self.auto_jump and newvalue > self.limits[self.marked_block][1] and self.marked_block < len(self.limits)-1:
666                                         self.handleKey(KEY_RIGHT)
667                                         self.handleKey(key)
668                                         return
669                                 else:
670                                         self._value[self.marked_block] = newvalue
671
672                         if len(str(self._value[self.marked_block])) >= self.block_len[self.marked_block]:
673                                 self.handleKey(KEY_RIGHT)
674
675                         self.validate()
676                         self.changed()
677
678         def genText(self):
679                 value = ""
680                 block_strlen = []
681                 for i in self._value:
682                         block_strlen.append(len(str(i)))        
683                         if value:
684                                 value += self.seperator
685                         value += str(i)
686                 leftPos = sum(block_strlen[:(self.marked_block)])+self.marked_block
687                 rightPos = sum(block_strlen[:(self.marked_block+1)])+self.marked_block
688                 mBlock = range(leftPos, rightPos)
689                 return (value, mBlock)
690         
691         def getMulti(self, selected):
692                 (value, mBlock) = self.genText()
693                 if self.enabled:
694                         return ("mtext"[1-selected:], value, mBlock)
695                 else:
696                         return ("text", value)
697
698         def getHTML(self, id):
699                 # we definitely don't want leading zeros
700                 return '.'.join(["%d" % d for d in self.value])
701
702 mac_limits = [(1,255),(1,255),(1,255),(1,255),(1,255),(1,255)]
703 class ConfigMAC(ConfigSequence):
704         def __init__(self, default):
705                 ConfigSequence.__init__(self, seperator = ":", limits = mac_limits, default = default)
706
707 class ConfigPosition(ConfigSequence):
708         def __init__(self, default, args):
709                 ConfigSequence.__init__(self, seperator = ",", limits = [(0,args[0]),(0,args[1]),(0,args[2]),(0,args[3])], default = default)
710
711 clock_limits = [(0,23),(0,59)]
712 class ConfigClock(ConfigSequence):
713         def __init__(self, default):
714                 t = localtime(default)
715                 ConfigSequence.__init__(self, seperator = ":", limits = clock_limits, default = [t.tm_hour, t.tm_min])
716
717         def increment(self):
718                 # Check if Minutes maxed out
719                 if self._value[1] == 59:
720                         # Increment Hour, reset Minutes
721                         if self._value[0] < 23:
722                                 self._value[0] += 1
723                         else:
724                                 self._value[0] = 0
725                         self._value[1] = 0
726                 else:
727                         # Increment Minutes
728                         self._value[1] += 1
729                 # Trigger change
730                 self.changed()
731
732         def decrement(self):
733                 # Check if Minutes is minimum
734                 if self._value[1] == 0:
735                         # Decrement Hour, set Minutes to 59
736                         if self._value[0] > 0:
737                                 self._value[0] -= 1
738                         else:
739                                 self._value[0] = 23
740                         self._value[1] = 59
741                 else:
742                         # Decrement Minutes
743                         self._value[1] -= 1
744                 # Trigger change
745                 self.changed()
746
747 integer_limits = (0, 9999999999)
748 class ConfigInteger(ConfigSequence):
749         def __init__(self, default, limits = integer_limits):
750                 ConfigSequence.__init__(self, seperator = ":", limits = [limits], default = default)
751         
752         # you need to override this to do input validation
753         def setValue(self, value):
754                 self._value = [value]
755                 self.changed()
756
757         def getValue(self):
758                 return self._value[0]
759
760         value = property(getValue, setValue)
761
762         def fromstring(self, value):
763                 return int(value)
764
765         def tostring(self, value):
766                 return str(value)
767
768 class ConfigPIN(ConfigInteger):
769         def __init__(self, default, len = 4, censor = ""):
770                 assert isinstance(default, int), "ConfigPIN default must be an integer"
771                 if default == -1:
772                         default = "aaaa"
773                 ConfigSequence.__init__(self, seperator = ":", limits = [(0, (10**len)-1)], censor_char = censor, default = default)
774                 self.len = len
775
776         def getLength(self):
777                 return self.len
778
779 class ConfigFloat(ConfigSequence):
780         def __init__(self, default, limits):
781                 ConfigSequence.__init__(self, seperator = ".", limits = limits, default = default)
782
783         def getFloat(self):
784                 return float(self.value[1] / float(self.limits[1][1] + 1) + self.value[0])
785
786         float = property(getFloat)
787
788 # an editable text...
789 class ConfigText(ConfigElement, NumericalTextInput):
790         def __init__(self, default = "", fixed_size = True, visible_width = False):
791                 ConfigElement.__init__(self)
792                 NumericalTextInput.__init__(self, nextFunc = self.nextFunc, handleTimeout = False)
793                 
794                 self.marked_pos = 0
795                 self.allmarked = (default != "")
796                 self.fixed_size = fixed_size
797                 self.visible_width = visible_width
798                 self.offset = 0
799                 self.overwrite = fixed_size
800                 self.help_window = None
801                 self.value = self.last_value = self.default = default
802
803         def validateMarker(self):
804                 if self.fixed_size:
805                         if self.marked_pos > len(self.text)-1:
806                                 self.marked_pos = len(self.text)-1
807                 else:
808                         if self.marked_pos > len(self.text):
809                                 self.marked_pos = len(self.text)
810                 if self.marked_pos < 0:
811                         self.marked_pos = 0
812                 if self.visible_width:
813                         if self.marked_pos < self.offset:
814                                 self.offset = self.marked_pos
815                         if self.marked_pos >= self.offset + self.visible_width:
816                                 if self.marked_pos == len(self.text):
817                                         self.offset = self.marked_pos - self.visible_width
818                                 else:
819                                         self.offset = self.marked_pos - self.visible_width + 1
820                         if self.offset > 0 and self.offset + self.visible_width > len(self.text):
821                                 self.offset = max(0, len(self.text) - self.visible_width)
822
823         def insertChar(self, ch, pos, owr):
824                 if owr or self.overwrite:
825                         self.text = self.text[0:pos] + ch + self.text[pos + 1:]
826                 elif self.fixed_size:
827                         self.text = self.text[0:pos] + ch + self.text[pos:-1]
828                 else:
829                         self.text = self.text[0:pos] + ch + self.text[pos:]
830
831         def deleteChar(self, pos):
832                 if not self.fixed_size:
833                         self.text = self.text[0:pos] + self.text[pos + 1:]
834                 elif self.overwrite:
835                         self.text = self.text[0:pos] + " " + self.text[pos + 1:]
836                 else:
837                         self.text = self.text[0:pos] + self.text[pos + 1:] + " "
838
839         def deleteAllChars(self):
840                 if self.fixed_size:
841                         self.text = " " * len(self.text)
842                 else:
843                         self.text = ""
844                 self.marked_pos = 0
845
846         def handleKey(self, key):
847                 # this will no change anything on the value itself
848                 # so we can handle it here in gui element
849                 if key == KEY_DELETE:
850                         self.timeout()
851                         if self.allmarked:
852                                 self.deleteAllChars()
853                                 self.allmarked = False
854                         else:
855                                 self.deleteChar(self.marked_pos)
856                                 if self.fixed_size and self.overwrite:
857                                         self.marked_pos += 1
858                 elif key == KEY_BACKSPACE:
859                         self.timeout()
860                         if self.allmarked:
861                                 self.deleteAllChars()
862                                 self.allmarked = False
863                         elif self.marked_pos > 0:
864                                 self.deleteChar(self.marked_pos-1)
865                                 if not self.fixed_size and self.offset > 0:
866                                         self.offset -= 1
867                                 self.marked_pos -= 1
868                 elif key == KEY_LEFT:
869                         self.timeout()
870                         if self.allmarked:
871                                 self.marked_pos = len(self.text)
872                                 self.allmarked = False
873                         else:
874                                 self.marked_pos -= 1
875                 elif key == KEY_RIGHT:
876                         self.timeout()
877                         if self.allmarked:
878                                 self.marked_pos = 0
879                                 self.allmarked = False
880                         else:
881                                 self.marked_pos += 1
882                 elif key == KEY_HOME:
883                         self.timeout()
884                         self.allmarked = False
885                         self.marked_pos = 0
886                 elif key == KEY_END:
887                         self.timeout()
888                         self.allmarked = False
889                         self.marked_pos = len(self.text)
890                 elif key == KEY_TOGGLEOW:
891                         self.timeout()
892                         self.overwrite = not self.overwrite
893                 elif key == KEY_ASCII:
894                         self.timeout()
895                         newChar = unichr(getPrevAsciiCode())
896                         if self.allmarked:
897                                 self.deleteAllChars()
898                                 self.allmarked = False
899                         self.insertChar(newChar, self.marked_pos, False)
900                         self.marked_pos += 1
901                 elif key in KEY_NUMBERS:
902                         owr = self.lastKey == getKeyNumber(key)
903                         newChar = self.getKey(getKeyNumber(key))
904                         if self.allmarked:
905                                 self.deleteAllChars()
906                                 self.allmarked = False
907                         self.insertChar(newChar, self.marked_pos, owr)
908                 elif key == KEY_TIMEOUT:
909                         self.timeout()
910                         if self.help_window:
911                                 self.help_window.update(self)
912                         return
913
914                 if self.help_window:
915                         self.help_window.update(self)
916                 self.validateMarker()
917                 self.changed()
918
919         def nextFunc(self):
920                 self.marked_pos += 1
921                 self.validateMarker()
922                 self.changed()
923
924         def getValue(self):
925                 return self.text.encode("utf-8")
926
927         def setValue(self, val):
928                 try:
929                         self.text = val.decode("utf-8")
930                 except UnicodeDecodeError:
931                         self.text = val.decode("utf-8", "ignore")
932                         print "Broken UTF8!"
933
934         value = property(getValue, setValue)
935         _value = property(getValue, setValue)
936
937         def getText(self):
938                 return self.text.encode("utf-8")
939
940         def getMulti(self, selected):
941                 if self.visible_width:
942                         if self.allmarked:
943                                 mark = range(0, min(self.visible_width, len(self.text)))
944                         else:
945                                 mark = [self.marked_pos-self.offset]
946                         return ("mtext"[1-selected:], self.text[self.offset:self.offset+self.visible_width].encode("utf-8")+" ", mark)
947                 else:
948                         if self.allmarked:
949                                 mark = range(0, len(self.text))
950                         else:
951                                 mark = [self.marked_pos]
952                         return ("mtext"[1-selected:], self.text.encode("utf-8")+" ", mark)
953
954         def onSelect(self, session):
955                 self.allmarked = (self.value != "")
956                 if session is not None:
957                         from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
958                         self.help_window = session.instantiateDialog(NumericalTextInputHelpDialog, self)
959                         self.help_window.show()
960
961         def onDeselect(self, session):
962                 self.marked_pos = 0
963                 self.offset = 0
964                 if self.help_window:
965                         session.deleteDialog(self.help_window)
966                         self.help_window = None
967                 if not self.last_value == self.value:
968                         self.changedFinal()
969                         self.last_value = self.value
970
971         def getHTML(self, id):
972                 return '<input type="text" name="' + id + '" value="' + self.value + '" /><br>\n'
973
974         def unsafeAssign(self, value):
975                 self.value = str(value)
976
977 class ConfigPassword(ConfigText):
978         def __init__(self, default = "", fixed_size = False, visible_width = False, censor = "*"):
979                 ConfigText.__init__(self, default = default, fixed_size = fixed_size, visible_width = visible_width)
980                 self.censor_char = censor
981                 self.hidden = True
982
983         def getMulti(self, selected):
984                 mtext, text, mark = ConfigText.getMulti(self, selected)
985                 if self.hidden:
986                         text = len(text) * self.censor_char
987                 return (mtext, text, mark)
988                         
989         def onSelect(self, session):
990                 ConfigText.onSelect(self, session)
991                 self.hidden = False
992
993         def onDeselect(self, session):
994                 ConfigText.onDeselect(self, session)
995                 self.hidden = True
996
997 class ConfigNumber(ConfigText):
998         def __init__(self, default = 0):
999                 ConfigText.__init__(self, str(default), fixed_size = False)
1000
1001         def getValue(self):
1002                 return int(self.text)
1003                 
1004         def setValue(self, val):
1005                 self.text = str(val)
1006
1007         value = property(getValue, setValue)
1008         _value = property(getValue, setValue)
1009
1010         def conform(self):
1011                 pos = len(self.text) - self.marked_pos
1012                 self.text = self.text.lstrip("0")
1013                 if self.text == "":
1014                         self.text = "0"
1015                 if pos > len(self.text):
1016                         self.marked_pos = 0
1017                 else:
1018                         self.marked_pos = len(self.text) - pos
1019
1020         def handleKey(self, key):
1021                 if key in KEY_NUMBERS or key == KEY_ASCII:
1022                         if key == KEY_ASCII:
1023                                 ascii = getPrevAsciiCode()
1024                                 if not (48 <= ascii <= 57):
1025                                         return
1026                         else:
1027                                 ascii = getKeyNumber(key) + 48
1028                         newChar = unichr(ascii)
1029                         if self.allmarked:
1030                                 self.deleteAllChars()
1031                                 self.allmarked = False
1032                         self.insertChar(newChar, self.marked_pos, False)
1033                         self.marked_pos += 1
1034                 else:
1035                         ConfigText.handleKey(self, key)
1036                 self.conform()
1037
1038         def onSelect(self, session):
1039                 self.allmarked = (self.value != "")
1040
1041         def onDeselect(self, session):
1042                 self.marked_pos = 0
1043                 self.offset = 0
1044                 if not self.last_value == self.value:
1045                         self.changedFinal()
1046                         self.last_value = self.value
1047
1048 class ConfigSearchText(ConfigText):
1049         def __init__(self, default = "", fixed_size = False, visible_width = False):
1050                 ConfigText.__init__(self, default = default, fixed_size = fixed_size, visible_width = visible_width)
1051                 NumericalTextInput.__init__(self, nextFunc = self.nextFunc, handleTimeout = False, search = True)
1052
1053 class ConfigDirectory(ConfigText):
1054         def __init__(self, default="", visible_width=60):
1055                 ConfigText.__init__(self, default, fixed_size = True, visible_width = visible_width)
1056         def handleKey(self, key):
1057                 pass
1058         def getValue(self):
1059                 if self.text == "":
1060                         return None
1061                 else:
1062                         return ConfigText.getValue(self)
1063         def setValue(self, val):
1064                 if val == None:
1065                         val = ""
1066                 ConfigText.setValue(self, val)
1067         def getMulti(self, selected):
1068                 if self.text == "":
1069                         return ("mtext"[1-selected:], _("List of Storage Devices"), range(0))
1070                 else:
1071                         return ConfigText.getMulti(self, selected)
1072
1073 # a slider.
1074 class ConfigSlider(ConfigElement):
1075         def __init__(self, default = 0, increment = 1, limits = (0, 100)):
1076                 ConfigElement.__init__(self)
1077                 self.value = self.last_value = self.default = default
1078                 self.min = limits[0]
1079                 self.max = limits[1]
1080                 self.increment = increment
1081
1082         def checkValues(self):
1083                 if self.value < self.min:
1084                         self.value = self.min
1085
1086                 if self.value > self.max:
1087                         self.value = self.max
1088
1089         def handleKey(self, key):
1090                 if key == KEY_LEFT:
1091                         self.value -= self.increment
1092                 elif key == KEY_RIGHT:
1093                         self.value += self.increment
1094                 elif key == KEY_HOME:
1095                         self.value = self.min
1096                 elif key == KEY_END:
1097                         self.value = self.max
1098                 else:
1099                         return
1100                 self.checkValues()
1101
1102         def getText(self):
1103                 return "%d / %d" % (self.value, self.max)
1104
1105         def getMulti(self, selected):
1106                 self.checkValues()
1107                 return ("slider", self.value, self.max)
1108
1109         def fromstring(self, value):
1110                 return int(value)
1111
1112 # a satlist. in fact, it's a ConfigSelection.
1113 class ConfigSatlist(ConfigSelection):
1114         def __init__(self, list, default = None):
1115                 if default is not None:
1116                         default = str(default)
1117                 ConfigSelection.__init__(self, choices = [(str(orbpos), desc) for (orbpos, desc, flags) in list], default = default)
1118
1119         def getOrbitalPosition(self):
1120                 if self.value == "":
1121                         return None
1122                 return int(self.value)
1123         
1124         orbital_position = property(getOrbitalPosition)
1125
1126 class ConfigSet(ConfigElement):
1127         def __init__(self, choices, default = []):
1128                 ConfigElement.__init__(self)
1129                 if isinstance(choices, list):
1130                         choices.sort()
1131                         self.choices = choicesList(choices, choicesList.LIST_TYPE_LIST)
1132                 else:
1133                         assert False, "ConfigSet choices must be a list!"
1134                 if default is None:
1135                         default = []
1136                 self.pos = -1
1137                 default.sort()
1138                 self.last_value = self.default = default
1139                 self.value = default[:]
1140
1141         def toggleChoice(self, choice):
1142                 if choice in self.value:
1143                         self.value.remove(choice)
1144                 else:
1145                         self.value.append(choice)
1146                         self.value.sort()
1147                 self.changed()
1148
1149         def handleKey(self, key):
1150                 if key in KEY_NUMBERS + [KEY_DELETE, KEY_BACKSPACE]:
1151                         if self.pos != -1:
1152                                 self.toggleChoice(self.choices[self.pos])
1153                 elif key == KEY_LEFT:
1154                         self.pos -= 1
1155                         if self.pos < -1:
1156                             self.pos = len(self.choices)-1
1157                 elif key == KEY_RIGHT:
1158                         self.pos += 1
1159                         if self.pos >= len(self.choices):
1160                             self.pos = -1
1161                 elif key in [KEY_HOME, KEY_END]:
1162                         self.pos = -1
1163
1164         def genString(self, lst):
1165                 res = ""
1166                 for x in lst:
1167                         res += self.description[x]+" "
1168                 return res
1169
1170         def getText(self):
1171                 return self.genString(self.value)
1172
1173         def getMulti(self, selected):
1174                 if not selected or self.pos == -1:
1175                         return ("text", self.genString(self.value))
1176                 else:
1177                         tmp = self.value[:]
1178                         ch = self.choices[self.pos]
1179                         mem = ch in self.value
1180                         if not mem:
1181                                 tmp.append(ch)
1182                                 tmp.sort()
1183                         ind = tmp.index(ch)
1184                         val1 = self.genString(tmp[:ind])
1185                         val2 = " "+self.genString(tmp[ind+1:])
1186                         if mem:
1187                                 chstr = " "+self.description[ch]+" "
1188                         else:
1189                                 chstr = "("+self.description[ch]+")"
1190                         return ("mtext", val1+chstr+val2, range(len(val1),len(val1)+len(chstr)))
1191
1192         def onDeselect(self, session):
1193                 self.pos = -1
1194                 if not self.last_value == self.value:
1195                         self.changedFinal()
1196                         self.last_value = self.value[:]
1197                 
1198         def tostring(self, value):
1199                 return str(value)
1200
1201         def fromstring(self, val):
1202                 return eval(val)
1203
1204         description = property(lambda self: descriptionList(self.choices.choices, choicesList.LIST_TYPE_LIST))
1205
1206 class ConfigLocations(ConfigElement):
1207         def __init__(self, default = [], visible_width = False):
1208                 ConfigElement.__init__(self)
1209                 self.visible_width = visible_width
1210                 self.pos = -1
1211                 self.default = default
1212                 self.locations = []
1213                 self.mountpoints = []
1214                 harddiskmanager.on_partition_list_change.append(self.mountpointsChanged)
1215                 self.value = default+[]
1216
1217         def setValue(self, value):
1218                 loc = [x[0] for x in self.locations if x[3]]
1219                 add = [x for x in value if not x in loc]
1220                 diff = add + [x for x in loc if not x in value]
1221                 self.locations = [x for x in self.locations if not x[0] in diff] + [[x, self.getMountpoint(x), True, True] for x in add]
1222                 self.locations.sort(key = lambda x: x[0])
1223                 self.changed()
1224
1225         def getValue(self):
1226                 self.checkChangedMountpoints()
1227                 for x in self.locations:
1228                         x[3] = x[2]
1229                 return [x[0] for x in self.locations if x[3]]
1230         
1231         value = property(getValue, setValue)
1232
1233         def tostring(self, value):
1234                 return str(value)
1235
1236         def fromstring(self, val):
1237                 return eval(val)
1238
1239         def load(self):
1240                 sv = self.saved_value
1241                 if sv is None:
1242                         tmp = self.default
1243                 else:
1244                         tmp = self.fromstring(sv)
1245                 self.locations = [[x, None, False, False] for x in tmp]
1246                 self.refreshMountpoints()
1247                 for x in self.locations:
1248                         if os_path.exists(x[0]):
1249                                 x[1] = self.getMountpoint(x[0])
1250                                 x[2] = True
1251
1252         def save(self):
1253                 if self.save_disabled or self.locations == []:
1254                         self.saved_value = None
1255                 else:
1256                         self.saved_value = self.tostring([x[0] for x in self.locations])
1257
1258         def isChanged(self):
1259                 sv = self.saved_value
1260                 if val is None and self.locations == []:
1261                         return False
1262                 return self.tostring([x[0] for x in self.locations]) != sv
1263
1264         def mountpointsChanged(self, action, dev):
1265                 print "Mounts changed: ", action, dev
1266                 mp = dev.mountpoint+"/"
1267                 if action == "add":
1268                         self.addedMount(mp)
1269                 elif action == "remove":
1270                         self.removedMount(mp)
1271                 self.refreshMountpoints()
1272
1273         def addedMount(self, mp):
1274                 for x in self.locations:
1275                         if x[1] == mp:
1276                                 x[2] = True
1277                         elif x[1] == None and os_path.exists(x[0]):
1278                                 x[1] = self.getMountpoint(x[0])
1279                                 x[2] = True
1280
1281         def removedMount(self, mp):
1282                 for x in self.locations:
1283                         if x[1] == mp:
1284                                 x[2] = False
1285                 
1286         def refreshMountpoints(self):
1287                 self.mountpoints = [p.mountpoint + "/" for p in harddiskmanager.getMountedPartitions() if p.mountpoint != "/"]
1288                 self.mountpoints.sort(key = lambda x: -len(x))
1289
1290         def checkChangedMountpoints(self):
1291                 oldmounts = self.mountpoints
1292                 self.refreshMountpoints()
1293                 if oldmounts == self.mountpoints:
1294                         return
1295                 for x in oldmounts:
1296                         if not x in self.mountpoints:
1297                                 self.removedMount(x)
1298                 for x in self.mountpoints:
1299                         if not x in oldmounts:
1300                                 self.addedMount(x)
1301
1302         def getMountpoint(self, file):
1303                 file = os_path.realpath(file)+"/"
1304                 for m in self.mountpoints:
1305                         if file.startswith(m):
1306                                 return m
1307                 return None
1308
1309         def handleKey(self, key):
1310                 if key == KEY_LEFT:
1311                         self.pos -= 1
1312                         if self.pos < -1:
1313                             self.pos = len(self.value)-1
1314                 elif key == KEY_RIGHT:
1315                         self.pos += 1
1316                         if self.pos >= len(self.value):
1317                             self.pos = -1
1318                 elif key in [KEY_HOME, KEY_END]:
1319                         self.pos = -1
1320
1321         def getText(self):
1322                 return " ".join(self.value)
1323
1324         def getMulti(self, selected):
1325                 if not selected:
1326                         valstr = " ".join(self.value)
1327                         if self.visible_width and len(valstr) > self.visible_width:
1328                                 return ("text", valstr[0:self.visible_width])
1329                         else:
1330                                 return ("text", valstr)
1331                 else:
1332                         i = 0
1333                         valstr = ""
1334                         ind1 = 0
1335                         ind2 = 0
1336                         for val in self.value:
1337                                 if i == self.pos:
1338                                         ind1 = len(valstr)
1339                                 valstr += str(val)+" "
1340                                 if i == self.pos:
1341                                         ind2 = len(valstr)
1342                                 i += 1
1343                         if self.visible_width and len(valstr) > self.visible_width:
1344                                 if ind1+1 < self.visible_width/2:
1345                                         off = 0
1346                                 else:
1347                                         off = min(ind1+1-self.visible_width/2, len(valstr)-self.visible_width)
1348                                 return ("mtext", valstr[off:off+self.visible_width], range(ind1-off,ind2-off))
1349                         else:
1350                                 return ("mtext", valstr, range(ind1,ind2))
1351
1352         def onDeselect(self, session):
1353                 self.pos = -1
1354                 
1355 # nothing.
1356 class ConfigNothing(ConfigSelection):
1357         def __init__(self):
1358                 ConfigSelection.__init__(self, choices = [""])
1359
1360 # until here, 'saved_value' always had to be a *string*.
1361 # now, in ConfigSubsection, and only there, saved_value
1362 # is a dict, essentially forming a tree.
1363 #
1364 # config.foo.bar=True
1365 # config.foobar=False
1366 #
1367 # turns into:
1368 # config.saved_value == {"foo": {"bar": "True"}, "foobar": "False"}
1369 #
1370
1371
1372 class ConfigSubsectionContent(object):
1373         pass
1374
1375 # we store a backup of the loaded configuration
1376 # data in self.stored_values, to be able to deploy
1377 # them when a new config element will be added,
1378 # so non-default values are instantly available
1379
1380 # A list, for example:
1381 # config.dipswitches = ConfigSubList()
1382 # config.dipswitches.append(ConfigYesNo())
1383 # config.dipswitches.append(ConfigYesNo())
1384 # config.dipswitches.append(ConfigYesNo())
1385 class ConfigSubList(list, object):
1386         def __init__(self):
1387                 object.__init__(self)
1388                 list.__init__(self)
1389                 self.stored_values = {}
1390
1391         def save(self):
1392                 for x in self:
1393                         x.save()
1394         
1395         def load(self):
1396                 for x in self:
1397                         x.load()
1398
1399         def getSavedValue(self):
1400                 res = { }
1401                 for i in range(len(self)):
1402                         sv = self[i].saved_value
1403                         if sv is not None:
1404                                 res[str(i)] = sv
1405                 return res
1406
1407         def setSavedValue(self, values):
1408                 self.stored_values = dict(values)
1409                 for (key, val) in self.stored_values.items():
1410                         if int(key) < len(self):
1411                                 self[int(key)].saved_value = val
1412
1413         saved_value = property(getSavedValue, setSavedValue)
1414
1415         def append(self, item):
1416                 i = str(len(self))
1417                 list.append(self, item)
1418                 if i in self.stored_values:
1419                         item.saved_value = self.stored_values[i]
1420                         item.load()
1421
1422         def dict(self):
1423                 return dict([(str(index), value) for index, value in self.enumerate()])
1424
1425 # same as ConfigSubList, just as a dictionary.
1426 # care must be taken that the 'key' has a proper
1427 # str() method, because it will be used in the config
1428 # file.
1429 class ConfigSubDict(dict, object):
1430         def __init__(self):
1431                 object.__init__(self)
1432                 dict.__init__(self)
1433                 self.stored_values = {}
1434
1435         def save(self):
1436                 for x in self.values():
1437                         x.save()
1438         
1439         def load(self):
1440                 for x in self.values():
1441                         x.load()
1442
1443         def getSavedValue(self):
1444                 res = {}
1445                 for (key, val) in self.items():
1446                         sv = val.saved_value
1447                         if sv is not None:
1448                                 res[str(key)] = sv
1449                 return res
1450
1451         def setSavedValue(self, values):
1452                 self.stored_values = dict(values)
1453                 for (key, val) in self.items():
1454                         if str(key) in self.stored_values:
1455                                 val = self.stored_values[str(key)]
1456
1457         saved_value = property(getSavedValue, setSavedValue)
1458
1459         def __setitem__(self, key, item):
1460                 dict.__setitem__(self, key, item)
1461                 if str(key) in self.stored_values:
1462                         item.saved_value = self.stored_values[str(key)]
1463                         item.load()
1464
1465         def dict(self):
1466                 return self
1467
1468 # Like the classes above, just with a more "native"
1469 # syntax.
1470 #
1471 # some evil stuff must be done to allow instant
1472 # loading of added elements. this is why this class
1473 # is so complex.
1474 #
1475 # we need the 'content' because we overwrite 
1476 # __setattr__.
1477 # If you don't understand this, try adding
1478 # __setattr__ to a usual exisiting class and you will.
1479 class ConfigSubsection(object):
1480         def __init__(self):
1481                 object.__init__(self)
1482                 self.__dict__["content"] = ConfigSubsectionContent()
1483                 self.content.items = { }
1484                 self.content.stored_values = { }
1485         
1486         def __setattr__(self, name, value):
1487                 if name == "saved_value":
1488                         return self.setSavedValue(value)
1489                 assert isinstance(value, (ConfigSubsection, ConfigElement, ConfigSubList, ConfigSubDict)), "ConfigSubsections can only store ConfigSubsections, ConfigSubLists, ConfigSubDicts or ConfigElements"
1490                 self.content.items[name] = value
1491                 x = self.content.stored_values.get(name, None)
1492                 if x is not None:
1493                         #print "ok, now we have a new item,", name, "and have the following value for it:", x
1494                         value.saved_value = x
1495                         value.load()
1496
1497         def __getattr__(self, name):
1498                 return self.content.items[name]
1499
1500         def getSavedValue(self):
1501                 res = self.content.stored_values
1502                 for (key, val) in self.content.items.items():
1503                         sv = val.saved_value
1504                         if sv is not None:
1505                                 res[key] = sv
1506                         elif key in res:
1507                                 del res[key]
1508                 return res
1509
1510         def setSavedValue(self, values):
1511                 values = dict(values)
1512                 self.content.stored_values = values
1513                 for (key, val) in self.content.items.items():
1514                         value = values.get(key, None)
1515                         if value is not None:
1516                                 val.saved_value = value
1517
1518         saved_value = property(getSavedValue, setSavedValue)
1519
1520         def save(self):
1521                 for x in self.content.items.values():
1522                         x.save()
1523
1524         def load(self):
1525                 for x in self.content.items.values():
1526                         x.load()
1527
1528         def dict(self):
1529                 return self.content.items
1530
1531 # the root config object, which also can "pickle" (=serialize)
1532 # down the whole config tree.
1533 #
1534 # we try to keep non-existing config entries, to apply them whenever
1535 # a new config entry is added to a subsection
1536 # also, non-existing config entries will be saved, so they won't be
1537 # lost when a config entry disappears.
1538 class Config(ConfigSubsection):
1539         def __init__(self):
1540                 ConfigSubsection.__init__(self)
1541
1542         def pickle_this(self, prefix, topickle, result):
1543                 for (key, val) in topickle.items():
1544                         name = ''.join((prefix, '.', key))
1545                         if isinstance(val, dict):
1546                                 self.pickle_this(name, val, result)
1547                         elif isinstance(val, tuple):
1548                                 result += [name, '=', val[0], '\n']
1549                         else:
1550                                 result += [name, '=', val, '\n']
1551
1552         def pickle(self):
1553                 result = []
1554                 self.pickle_this("config", self.saved_value, result)
1555                 return ''.join(result)
1556
1557         def unpickle(self, lines):
1558                 tree = { }
1559                 for l in lines:
1560                         if not l or l[0] == '#':
1561                                 continue
1562                         
1563                         n = l.find('=')
1564                         val = l[n+1:].strip()
1565
1566                         names = l[:n].split('.')
1567 #                       if val.find(' ') != -1:
1568 #                               val = val[:val.find(' ')]
1569
1570                         base = tree
1571                         
1572                         for n in names[:-1]:
1573                                 base = base.setdefault(n, {})
1574                         
1575                         base[names[-1]] = val
1576
1577                 # we inherit from ConfigSubsection, so ...
1578                 #object.__setattr__(self, "saved_value", tree["config"])
1579                 if "config" in tree:
1580                         self.setSavedValue(tree["config"])
1581
1582         def saveToFile(self, filename):
1583                 f = open(filename, "w")
1584                 f.write(self.pickle())
1585                 f.close()
1586
1587         def loadFromFile(self, filename):
1588                 f = open(filename, "r")
1589                 self.unpickle(f.readlines())
1590                 f.close()
1591
1592 config = Config()
1593 config.misc = ConfigSubsection()
1594
1595 class ConfigFile:
1596         CONFIG_FILE = resolveFilename(SCOPE_CONFIG, "settings")
1597
1598         def load(self):
1599                 try:
1600                         config.loadFromFile(self.CONFIG_FILE)
1601                 except IOError, e:
1602                         print "unable to load config (%s), assuming defaults..." % str(e)
1603         
1604         def save(self):
1605 #               config.save()
1606                 config.saveToFile(self.CONFIG_FILE)
1607         
1608         def __resolveValue(self, pickles, cmap):
1609                 if cmap.has_key(pickles[0]):
1610                         if len(pickles) > 1:
1611                                 return self.__resolveValue(pickles[1:], cmap[pickles[0]].dict())
1612                         else:
1613                                 return str(cmap[pickles[0]].value)
1614                 return None
1615         
1616         def getResolvedKey(self, key):
1617                 names = key.split('.')
1618                 if len(names) > 1:
1619                         if names[0] == "config":
1620                                 ret=self.__resolveValue(names[1:], config.content.items)
1621                                 if ret and len(ret):
1622                                         return ret
1623                 print "getResolvedKey", key, "failed !! (Typo??)"
1624                 return ""
1625
1626 def NoSave(element):
1627         element.disableSave()
1628         return element
1629
1630 configfile = ConfigFile()
1631
1632 configfile.load()
1633
1634 def getConfigListEntry(*args):
1635         assert len(args) > 1, "getConfigListEntry needs a minimum of two arguments (descr, configElement)"
1636         return args
1637
1638 def updateConfigElement(element, newelement):
1639         newelement.value = element.value
1640         return newelement
1641
1642 #def _(x):
1643 #       return x
1644 #
1645 #config.bla = ConfigSubsection()
1646 #config.bla.test = ConfigYesNo()
1647 #config.nim = ConfigSubList()
1648 #config.nim.append(ConfigSubsection())
1649 #config.nim[0].bla = ConfigYesNo()
1650 #config.nim.append(ConfigSubsection())
1651 #config.nim[1].bla = ConfigYesNo()
1652 #config.nim[1].blub = ConfigYesNo()
1653 #config.arg = ConfigSubDict()
1654 #config.arg["Hello"] = ConfigYesNo()
1655 #
1656 #config.arg["Hello"].handleKey(KEY_RIGHT)
1657 #config.arg["Hello"].handleKey(KEY_RIGHT)
1658 #
1659 ##config.saved_value
1660 #
1661 ##configfile.save()
1662 #config.save()
1663 #print config.pickle()