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