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