1 from enigma import eRCInput, getPrevAsciiCode
2 from Tools.NumericalTextInput import NumericalTextInput, NumericalHexInput
3 from Tools.Directories import resolveFilename, SCOPE_CONFIG, fileExists
4 from Tools.IO import saveFile
5 from copy import copy as copy_copy
6 from os import path as os_path
7 from time import localtime, strftime
8 from netaddr import IPAddress
9 from ast import literal_eval
11 from Tools.Log import Log
12 # ConfigElement, the base class of all ConfigElements.
15 # value the current value, usefully encoded.
16 # usually a property which retrieves _value,
17 # and maybe does some reformatting
18 # _value the value as it's going to be saved in the configfile,
19 # though still in non-string form.
20 # this is the object which is actually worked on.
21 # default the initial value. If _value is equal to default,
22 # it will not be stored in the config file
23 # saved_value is a text representation of _value, stored in the config file
25 # and has (at least) the following methods:
26 # save() stores _value into saved_value,
27 # (or stores 'None' if it should not be stored)
28 # load() loads _value from saved_value, or loads
29 # the default if saved_value is 'None' (default)
32 class ConfigElement(object):
34 self.saved_value = None
35 self.save_forced = True
36 self.save_disabled = False
37 self.__notifiers = { }
38 self.__notifiers_final = { }
42 def getNotifiers(self):
43 return [func for (key, func, val, call_on_save_and_cancel) in self.__notifiers.itervalues()]
45 def setNotifiers(self, val):
46 print "just readonly access to notifiers is allowed! append/remove doesnt work anymore! please use addNotifier, removeNotifier, clearNotifiers"
48 notifiers = property(getNotifiers, setNotifiers)
50 def getNotifiersFinal(self):
51 return [func for (key, func, val, call_on_save_and_cancel) in self.__notifiers_final.itervalues()]
53 def setNotifiersFinal(self, val):
54 print "just readonly access to notifiers_final is allowed! append/remove doesnt work anymore! please use addNotifier, removeNotifier, clearNotifiers"
56 notifiers_final = property(getNotifiersFinal, setNotifiersFinal)
58 # you need to override this to do input validation
59 def setValue(self, value):
66 value = property(getValue, setValue)
68 # you need to override this if self.value is not a string
69 def fromstring(self, value):
72 # you can overide this for fancy default handling
76 self.value = self.default
78 self.value = self.fromstring(sv)
80 def tostring(self, value):
83 # you need to override this if str(self.value) doesn't work
85 if self.save_disabled or (self.value == self.default and not self.save_forced):
86 self.saved_value = None
88 self.saved_value = self.tostring(self.value)
89 self.changed(save_or_cancel=True)
90 self.changedFinal(save_or_cancel=True)
94 self.changed(save_or_cancel=True)
95 self.changedFinal(save_or_cancel=True)
99 if sv is None and self.value == self.default:
101 return self.tostring(self.value) != sv
103 def changed(self, save_or_cancel=False):
104 for (func, val) in sorted(self.__notifiers.iteritems()):
105 strval = str(self.value)
106 if (val[3] and save_or_cancel) or (save_or_cancel == False and val[2] != strval):
107 self.__notifiers[func] = (val[0], val[1], strval, val[3])
110 def changedFinal(self, save_or_cancel=False):
111 for (func, val) in sorted(self.__notifiers_final.iteritems()):
112 strval = str(self.value)
113 if (val[3] and save_or_cancel) or (save_or_cancel == False and val[2] != strval):
114 self.__notifiers_final[func] = (val[0], val[1], strval, val[3])
117 # immediate_feedback = True means call notifier on every value CHANGE
118 # immediate_feedback = False means call notifier on leave the config element (up/down) when value have CHANGED
119 # call_on_save_or_cancel = True means call notifier always on save/cancel.. even when value have not changed
120 def addNotifier(self, notifier, initial_call = True, immediate_feedback = True, call_on_save_or_cancel = False):
121 assert callable(notifier), "notifiers must be callable"
123 # repr(notifier) is useless for private functions starting with __
124 key = 'addNotifier {1} <{0} instance at {2}>'.format(notifier.__module__, notifier.__func__, hex(id(notifier.__self__)))
127 if immediate_feedback:
128 self.__notifiers[key] = (self.key, notifier, str(self.value), call_on_save_or_cancel)
130 self.__notifiers_final[key] = (self.key, notifier, str(self.value), call_on_save_or_cancel)
133 # do we want to call the notifier
134 # - at all when adding it? (yes, though optional)
135 # - when the default is active? (yes)
136 # - when no value *yet* has been set,
137 # because no config has ever been read (currently yes)
138 # (though that's not so easy to detect.
139 # the entry could just be new.)
143 def removeNotifier(self, notifier):
145 n = 'addNotifier {1} <{0} instance at {2}>'.format(notifier.__module__, notifier.__func__, hex(id(notifier.__self__)))
148 if n in self.__notifiers:
149 del self.__notifiers[n]
150 if n in self.__notifiers_final:
151 del self.__notifiers_final[n]
153 def clearNotifiers(self):
154 self.__notifiers = { }
156 def disableSave(self):
157 self.save_disabled = True
159 def __call__(self, selected):
160 return self.getMulti(selected)
162 def onSelect(self, session):
165 def onDeselect(self, session):
178 KEY_NUMBERS = range(12, 12+10)
182 def getKeyNumber(key):
183 assert key in KEY_NUMBERS
186 class choicesList(object): # XXX: we might want a better name for this
190 def __init__(self, choices, type = None):
191 self.choices = choices
192 self.__itemDescriptionUpdatedCallbacks = []
195 if isinstance(choices, list):
196 self.type = choicesList.LIST_TYPE_LIST
197 elif isinstance(choices, dict):
198 self.type = choicesList.LIST_TYPE_DICT
200 assert False, "choices must be dict or list!"
205 if self.type == choicesList.LIST_TYPE_LIST:
206 ret = [not isinstance(x, tuple) and x or x[0] for x in self.choices]
208 ret = self.choices.keys()
212 if self.type == choicesList.LIST_TYPE_LIST:
213 ret = [not isinstance(x, tuple) and x or x[0] for x in self.choices]
216 return iter(ret or [""])
219 return len(self.choices) or 1
221 def additemDescriptionUpdatedCallback(self, callback):
222 self.__itemDescriptionUpdatedCallbacks.append(callback)
224 def updateItemDescription(self, index, descr):
225 if self.type == choicesList.LIST_TYPE_LIST:
226 orig = self.choices[index]
227 assert isinstance(orig, tuple)
228 self.choices[index] = (orig[0], descr)
230 key = self.choices.keys()[index]
231 self.choices[key] = descr
232 for cb in self.__itemDescriptionUpdatedCallbacks:
236 from traceback import print_exc
237 print "WARNING: callback", cb, "in config.choicesList.__itemDescriptionUpdatedCallbacks throws exception (removing callback):"
239 self.__itemDescriptionUpdatedCallbacks.remove(cb)
241 def __getitem__(self, index):
242 if self.type == choicesList.LIST_TYPE_LIST:
243 ret = self.choices[index]
244 if isinstance(ret, tuple):
247 return self.choices.keys()[index]
249 def index(self, value):
250 return self.__list__().index(value)
252 def __setitem__(self, index, value):
253 if self.type == choicesList.LIST_TYPE_LIST:
254 orig = self.choices[index]
255 if isinstance(orig, tuple):
256 self.choices[index] = (value, orig[1])
258 self.choices[index] = value
260 key = self.choices.keys()[index]
261 orig = self.choices[key]
262 del self.choices[key]
263 self.choices[value] = orig
266 choices = self.choices
269 if self.type is choicesList.LIST_TYPE_LIST:
271 if isinstance(default, tuple):
274 default = choices.keys()[0]
277 class descriptionList(choicesList): # XXX: we might want a better name for this
279 if self.type == choicesList.LIST_TYPE_LIST:
280 ret = [not isinstance(x, tuple) and x or x[1] for x in self.choices]
282 ret = self.choices.values()
286 return iter(self.__list__())
288 def __getitem__(self, index):
289 if self.type == choicesList.LIST_TYPE_LIST:
290 for x in self.choices:
291 if isinstance(x, tuple):
296 return str(index) # Fallback!
298 return str(self.choices.get(index, ""))
300 def __setitem__(self, index, value):
301 if self.type == choicesList.LIST_TYPE_LIST:
302 i = self.index(index)
303 orig = self.choices[i]
304 if isinstance(orig, tuple):
305 self.choices[i] = (orig[0], value)
307 self.choices[i] = value
309 self.choices[index] = value
312 # ConfigSelection is a "one of.."-type.
313 # it has the "choices", usually a list, which contains
314 # (id, desc)-tuples (or just only the ids, in case the id
315 # will be used as description)
317 # all ids MUST be plain strings.
319 class ConfigSelection(ConfigElement):
320 def __init__(self, choices, default = None):
321 ConfigElement.__init__(self)
322 self.choices = choicesList(choices)
323 self.choices.additemDescriptionUpdatedCallback(self._invalidateCachedDescription)
326 default = self.choices.default()
328 self._invalidateCachedDescription()
329 self.default = self._value = default
331 def setChoices(self, choices, default = None):
332 self.choices = choicesList(choices)
333 self.choices.additemDescriptionUpdatedCallback(self._invalidateCachedDescription)
336 default = self.choices.default()
337 self.default = default
339 if self.value not in self.choices:
341 self._invalidateCachedDescription()
344 def _invalidateCachedDescription(self):
347 def getChoices(self):
348 return self.choices.choices
350 def setValue(self, value):
351 if value in self.choices:
354 self._value = self.default
358 def tostring(self, val):
364 def setCurrentText(self, text):
365 i = self.choices.index(self.value)
366 self.choices[i] = text
367 self._descr = self.description[text] = text
370 value = property(getValue, setValue)
373 return self.choices.index(self.value)
375 index = property(getIndex)
378 def handleKey(self, key):
379 nchoices = len(self.choices)
380 i = self.choices.index(self.value)
382 self.value = self.choices[(i + nchoices - 1) % nchoices]
383 elif key == KEY_RIGHT:
384 self.value = self.choices[(i + 1) % nchoices]
385 elif key == KEY_HOME:
386 self.value = self.choices[0]
388 self.value = self.choices[nchoices - 1]
390 def selectNext(self):
391 nchoices = len(self.choices)
392 i = self.choices.index(self.value)
393 self.value = self.choices[(i + 1) % nchoices]
396 if self._descr is not None:
398 descr = self._descr = self.description[self.value]
403 def getMulti(self, selected):
404 if self._descr is not None:
407 descr = self._descr = self.description[self.value]
409 return ("text", _(descr))
410 return ("text", descr)
413 def getHTML(self, id):
415 for v in self.choices:
416 descr = self.description[v]
418 checked = 'checked="checked" '
421 res += '<input type="radio" name="' + id + '" ' + checked + 'value="' + v + '">' + descr + "</input></br>\n"
424 def unsafeAssign(self, value):
425 # setValue does check if value is in choices. This is safe enough.
428 description = property(lambda self: descriptionList(self.choices.choices, self.choices.type))
432 # several customized versions exist for different
435 boolean_descriptions = {False: "false", True: "true"}
436 class ConfigBoolean(ConfigElement):
439 def setOnOffPixmaps(onPixmap, offPixmap):
440 ConfigBoolean._onOffPixmaps = {
445 def __init__(self, default = False, descriptions = boolean_descriptions):
446 ConfigElement.__init__(self)
447 self.descriptions = descriptions
448 self.value = self.default = default
450 def handleKey(self, key):
451 if key in (KEY_LEFT, KEY_RIGHT):
452 self.value = not self.value
453 elif key == KEY_HOME:
459 descr = self.descriptions[self.value]
464 def getMulti(self, selected):
465 if ConfigBoolean._onOffPixmaps:
466 return ("pixmap", ConfigBoolean._onOffPixmaps[self.value])
467 descr = self.descriptions[self.value]
469 return ("text", _(descr))
470 return ("text", descr)
472 def tostring(self, value):
478 def fromstring(self, val):
484 def getHTML(self, id):
486 checked = ' checked="checked"'
489 return '<input type="checkbox" name="' + id + '" value="1" ' + checked + " />"
491 # this is FLAWED. and must be fixed.
492 def unsafeAssign(self, value):
498 yes_no_descriptions = {False: _("no"), True: _("yes")}
499 class ConfigYesNo(ConfigBoolean):
500 def __init__(self, default = False):
501 ConfigBoolean.__init__(self, default = default, descriptions = yes_no_descriptions)
503 on_off_descriptions = {False: _("off"), True: _("on")}
504 class ConfigOnOff(ConfigBoolean):
505 def __init__(self, default = False):
506 ConfigBoolean.__init__(self, default = default, descriptions = on_off_descriptions)
508 class ConfigEnableDisable(ConfigOnOff):
509 def __init__(self, default = False):
510 print "WARNING! ConfigEnableDisable is deprecated, please use ConfigOnOff instead!"
511 ConfigOnOff.__init__(self, default = default)
513 class ConfigDateTime(ConfigElement):
514 def __init__(self, default, formatstring, increment = 86400):
515 ConfigElement.__init__(self)
516 self.increment = increment
517 self.formatstring = formatstring
518 self.value = self.default = int(default)
520 def handleKey(self, key):
522 self.value = self.value - self.increment
523 elif key == KEY_RIGHT:
524 self.value = self.value + self.increment
525 elif key == KEY_HOME or key == KEY_END:
526 self.value = self.default
529 return strftime(self.formatstring, localtime(self.value))
531 def getMulti(self, selected):
532 return ("text", strftime(self.formatstring, localtime(self.value)))
534 def fromstring(self, val):
537 # *THE* mighty config element class
539 # allows you to store/edit a sequence of values.
540 # can be used for IP-addresses, dates, plain integers, ...
541 # several helper exist to ease this up a bit.
543 class ConfigSequence(ConfigElement):
544 def __init__(self, seperator, limits, default, censor_char = ""):
545 ConfigElement.__init__(self)
546 assert isinstance(limits, list) and len(limits[0]) == 2, "limits must be [(min, max),...]-tuple-list"
547 assert censor_char == "" or len(censor_char) == 1, "censor char must be a single char (or \"\")"
548 #assert isinstance(default, list), "default must be a list"
549 #assert isinstance(default[0], int), "list must contain numbers"
550 #assert len(default) == len(limits), "length must match"
553 self.seperator = seperator
555 self.censor_char = censor_char
557 self.default = default
558 self.value = copy_copy(default)
559 self.endNotifier = None
564 for i in self._value:
565 max_pos += len(str(self.limits[num][1]))
567 if self._value[num] < self.limits[num][0]:
568 self._value[num] = self.limits[num][0]
570 if self._value[num] > self.limits[num][1]:
571 self._value[num] = self.limits[num][1]
575 if self.marked_pos >= max_pos:
577 for x in self.endNotifier:
579 self.marked_pos = max_pos - 1
581 if self.marked_pos < 0:
584 def validatePos(self):
585 if self.marked_pos < 0:
588 total_len = sum([len(str(x[1])) for x in self.limits])
590 if self.marked_pos >= total_len:
591 self.marked_pos = total_len - 1
593 def addEndNotifier(self, notifier):
594 if self.endNotifier is None:
595 self.endNotifier = []
596 self.endNotifier.append(notifier)
598 def handleKey(self, key):
603 elif key == KEY_RIGHT:
607 elif key == KEY_HOME:
614 for i in self._value:
615 max_pos += len(str(self.limits[num][1]))
617 self.marked_pos = max_pos - 1
620 elif key in KEY_NUMBERS or key == KEY_ASCII:
622 code = getPrevAsciiCode()
623 if code < 48 or code > 57:
627 number = getKeyNumber(key)
629 block_len = [len(str(x[1])) for x in self.limits]
633 block_len_total = [0, ]
635 pos += block_len[blocknumber]
636 block_len_total.append(pos)
637 if pos - 1 >= self.marked_pos:
642 # length of numberblock
643 number_len = len(str(self.limits[blocknumber][1]))
645 # position in the block
646 posinblock = self.marked_pos - block_len_total[blocknumber]
648 oldvalue = self._value[blocknumber]
649 olddec = oldvalue % 10 ** (number_len - posinblock) - (oldvalue % 10 ** (number_len - posinblock - 1))
650 newvalue = oldvalue - olddec + (10 ** (number_len - posinblock - 1) * number)
652 self._value[blocknumber] = newvalue
660 mPos = self.marked_pos
662 for i in self._value:
663 if value: #fixme no heading separator possible
664 value += self.seperator
665 if mPos >= len(value) - 1:
667 if self.censor_char == "":
668 value += ("%0" + str(len(str(self.limits[num][1]))) + "d") % i
670 value += (self.censor_char * len(str(self.limits[num][1])))
675 (value, mPos) = self.genText()
678 def getMulti(self, selected):
679 (value, mPos) = self.genText()
680 # only mark cursor when we are selected
681 # (this code is heavily ink optimized!)
683 return ("mtext"[1-selected:], value, [mPos])
685 return ("text", value)
687 def tostring(self, val):
688 return self.seperator.join([self.saveSingle(x) for x in val])
690 def saveSingle(self, v):
693 def fromstring(self, value):
695 return [int(x) for x in value.split(self.seperator)]
699 ip_limits = [(0,255),(0,255),(0,255),(0,255)]
700 class ConfigIP(ConfigSequence):
701 def __init__(self, default, auto_jump = False):
702 ConfigSequence.__init__(self, seperator = ".", limits = ip_limits, default = default)
703 self.block_len = [len(str(x[1])) for x in self.limits]
704 self.marked_block = 0
705 self.overwrite = True
706 self.auto_jump = auto_jump
708 def handleKey(self, key):
710 if self.marked_block > 0:
711 self.marked_block -= 1
712 self.overwrite = True
714 elif key == KEY_RIGHT:
715 if self.marked_block < len(self.limits)-1:
716 self.marked_block += 1
717 self.overwrite = True
719 elif key == KEY_HOME:
720 self.marked_block = 0
721 self.overwrite = True
724 self.marked_block = len(self.limits)-1
725 self.overwrite = True
727 elif key in KEY_NUMBERS or key == KEY_ASCII:
729 code = getPrevAsciiCode()
730 if code < 48 or code > 57:
734 number = getKeyNumber(key)
735 oldvalue = self._value[self.marked_block]
738 self._value[self.marked_block] = number
739 self.overwrite = False
742 newvalue = oldvalue + number
743 if self.auto_jump and newvalue > self.limits[self.marked_block][1] and self.marked_block < len(self.limits)-1:
744 self.handleKey(KEY_RIGHT)
748 self._value[self.marked_block] = newvalue
750 if len(str(self._value[self.marked_block])) >= self.block_len[self.marked_block]:
751 self.handleKey(KEY_RIGHT)
759 for i in self._value:
760 block_strlen.append(len(str(i)))
762 value += self.seperator
764 leftPos = sum(block_strlen[:(self.marked_block)])+self.marked_block
765 rightPos = sum(block_strlen[:(self.marked_block+1)])+self.marked_block
766 mBlock = range(leftPos, rightPos)
767 return (value, mBlock)
769 def getMulti(self, selected):
770 (value, mBlock) = self.genText()
772 return ("mtext"[1-selected:], value, mBlock)
774 return ("text", value)
776 def getHTML(self, id):
777 # we definitely don't want leading zeros
778 return '.'.join(["%d" % d for d in self.value])
780 mac_limits = [(1,255),(1,255),(1,255),(1,255),(1,255),(1,255)]
781 class ConfigMAC(ConfigSequence):
782 def __init__(self, default):
783 ConfigSequence.__init__(self, seperator = ":", limits = mac_limits, default = default)
785 class ConfigPosition(ConfigSequence):
786 def __init__(self, default, args):
787 ConfigSequence.__init__(self, seperator = ",", limits = [(0,args[0]),(0,args[1]),(0,args[2]),(0,args[3])], default = default)
789 clock_limits = [(0,23),(0,59)]
790 class ConfigClock(ConfigSequence):
791 def __init__(self, default):
792 t = localtime(default)
793 ConfigSequence.__init__(self, seperator = ":", limits = clock_limits, default = [t.tm_hour, t.tm_min])
796 # Check if Minutes maxed out
797 if self._value[1] == 59:
798 # Increment Hour, reset Minutes
799 if self._value[0] < 23:
811 # Check if Minutes is minimum
812 if self._value[1] == 0:
813 # Decrement Hour, set Minutes to 59
814 if self._value[0] > 0:
825 integer_limits = (0, 9999999999)
826 class ConfigInteger(ConfigSequence):
827 def __init__(self, default, limits = integer_limits):
828 ConfigSequence.__init__(self, seperator = ":", limits = [limits], default = default)
830 # you need to override this to do input validation
831 def setValue(self, value):
832 self._value = [int(value)]
836 return self._value[0]
838 value = property(getValue, setValue)
840 def fromstring(self, value):
843 def tostring(self, value):
846 class ConfigPIN(ConfigInteger):
847 def __init__(self, default, len = 4, censor = ""):
848 assert isinstance(default, int), "ConfigPIN default must be an integer"
849 ConfigSequence.__init__(self, seperator = ":", limits = [(0, (10**len)-1)], censor_char = censor, default = default)
855 class ConfigFloat(ConfigSequence):
856 def __init__(self, default, limits):
857 ConfigSequence.__init__(self, seperator = ".", limits = limits, default = default)
860 return float(self.value[1] / float(self.limits[1][1] + 1) + self.value[0])
862 float = property(getFloat)
864 # an editable text...
865 class ConfigTextBase(ConfigElement):
866 def __init__(self, default = "", fixed_size = True, visible_width = False, censor_char = ''):
867 ConfigElement.__init__(self)
868 self.selected = False
869 self.censor_char = censor_char
871 self.allmarked = (default != "")
872 self.fixed_size = fixed_size
873 self.visible_width = visible_width
875 self.overwrite = fixed_size
876 self.help_window = None
877 self.value = self.default = default
878 self._keyboardMode = 0
880 def validateMarker(self):
881 textlen = len(self.text)
883 if self.marked_pos > textlen-1:
884 self.marked_pos = textlen-1
886 if self.marked_pos > textlen:
887 self.marked_pos = textlen
888 if self.marked_pos < 0:
890 if self.visible_width:
891 if self.marked_pos < self.offset:
892 self.offset = self.marked_pos
893 if self.marked_pos >= self.offset + self.visible_width:
894 if self.marked_pos == textlen:
895 self.offset = self.marked_pos - self.visible_width
897 self.offset = self.marked_pos - self.visible_width + 1
898 if self.offset > 0 and self.offset + self.visible_width > textlen:
899 self.offset = max(0, textlen - self.visible_width)
901 def insertChar(self, ch, pos, owr):
902 if owr or self.overwrite:
903 self.text = self.text[0:pos] + ch + self.text[pos + 1:]
904 elif self.fixed_size:
905 self.text = self.text[0:pos] + ch + self.text[pos:-1]
907 self.text = self.text[0:pos] + ch + self.text[pos:]
909 def deleteChar(self, pos):
910 if not self.fixed_size:
911 self.text = self.text[0:pos] + self.text[pos + 1:]
913 self.text = self.text[0:pos] + " " + self.text[pos + 1:]
915 self.text = self.text[0:pos] + self.text[pos + 1:] + " "
917 def deleteAllChars(self):
919 self.text = " " * len(self.text)
924 def handleKey(self, key):
925 # this will no change anything on the value itself
926 # so we can handle it here in gui element
927 if key == KEY_DELETE:
930 self.deleteAllChars()
931 self.allmarked = False
933 self.deleteChar(self.marked_pos)
934 if self.fixed_size and self.overwrite:
936 elif key == KEY_BACKSPACE:
939 self.deleteAllChars()
940 self.allmarked = False
941 elif self.marked_pos > 0:
942 self.deleteChar(self.marked_pos-1)
943 if not self.fixed_size and self.offset > 0:
946 elif key == KEY_LEFT:
949 self.marked_pos = len(self.text)
950 self.allmarked = False
953 elif key == KEY_RIGHT:
957 self.allmarked = False
960 elif key == KEY_HOME:
962 self.allmarked = False
966 self.allmarked = False
967 self.marked_pos = len(self.text)
968 elif key == KEY_TOGGLEOW:
970 self.overwrite = not self.overwrite
971 elif key == KEY_ASCII:
973 newChar = unichr(getPrevAsciiCode())
974 if not self.useableChars or newChar in self.useableChars:
976 self.deleteAllChars()
977 self.allmarked = False
978 self.insertChar(newChar, self.marked_pos, False)
980 elif key in KEY_NUMBERS:
981 owr = self.lastKey == getKeyNumber(key)
982 newChar = self.getKey(getKeyNumber(key))
984 self.deleteAllChars()
985 self.allmarked = False
986 self.insertChar(newChar, self.marked_pos, owr)
987 elif key == KEY_TIMEOUT:
990 self.help_window.update(self)
994 self.help_window.update(self)
995 self.validateMarker()
1000 self.validateMarker()
1004 return self.text.encode("utf-8")
1006 def setValue(self, val):
1008 self.text = val.decode("utf-8")
1009 except UnicodeDecodeError:
1010 self.text = val.decode("utf-8", "ignore")
1011 print "Broken UTF8!"
1014 value = property(getValue, setValue)
1015 _value = property(getValue, setValue)
1018 tlen = len(self.text)
1019 if tlen and self.censor_char != '':
1020 tmp = [ self.censor_char for c in range(tlen) ]
1022 mpos = self.marked_pos
1025 tmp[mpos] = self.text[mpos]
1026 text = u''.join(tmp)
1032 return self.genText()
1034 def getMulti(self, selected):
1035 text = self.genText()
1036 if self.visible_width:
1038 mark = range(0, min(self.visible_width, len(text)))
1040 mark = [self.marked_pos-self.offset]
1041 return ("mtext"[1-selected:], text[self.offset:self.offset+self.visible_width].encode("utf-8")+" ", mark)
1044 mark = range(0, len(text))
1046 mark = [self.marked_pos]
1047 return ("mtext"[1-selected:], text.encode("utf-8")+" ", mark)
1049 def onSelect(self, session):
1050 if not self.enabled:
1051 self.allmarked = False
1052 self.marked_pos = -1
1054 self.selected = True
1055 self.allmarked = (self.value != "")
1056 self._keyboardMode = eRCInput.getInstance().getKeyboardMode()
1057 eRCInput.getInstance().setKeyboardMode(eRCInput.kmAscii)
1058 if session is not None:
1059 from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
1060 self.help_window = session.instantiateDialog(NumericalTextInputHelpDialog, self, zPosition=5000)
1061 self.help_window.neverAnimate()
1062 self.help_window.show()
1064 Log.w("Help dialog skipped - session is None!")
1066 def onDeselect(self, session):
1067 self.selected = False
1068 eRCInput.getInstance().setKeyboardMode(self._keyboardMode)
1071 if self.help_window:
1072 session.deleteDialog(self.help_window)
1073 self.help_window = None
1074 ConfigElement.onDeselect(self, session)
1076 def getHTML(self, id):
1077 return '<input type="text" name="' + id + '" value="' + self.value + '" /><br>\n'
1079 def unsafeAssign(self, value):
1080 self.value = str(value)
1082 class ConfigText(ConfigTextBase, NumericalTextInput):
1083 def __init__(self, default = "", fixed_size = True, visible_width = False, censor_char = ''):
1084 ConfigTextBase.__init__(self, default = default, fixed_size = fixed_size, visible_width = visible_width, censor_char = censor_char)
1085 NumericalTextInput.__init__(self, nextFunc = self.nextFunc, handleTimeout = False)
1087 class ConfigHexSequence(ConfigTextBase, NumericalHexInput):
1088 def __init__(self, default="000000", fixed_size = True, visible_width = False, censor_char = ''):
1089 ConfigTextBase.__init__(self, default = default, fixed_size = fixed_size, visible_width = visible_width)
1090 NumericalHexInput.__init__(self, nextFunc = self.nextFunc, handleTimeout = False)
1092 #IPv6 Address with validation and autoformatting
1093 class ConfigIP6(ConfigTextBase, NumericalHexInput):
1094 def __init__(self, default = "::", fixed_size=False, visible_width=False):
1095 ConfigTextBase.__init__(self, default = default, fixed_size = fixed_size, visible_width = visible_width)
1096 NumericalHexInput.__init__(self, nextFunc = self.nextFunc, handleTimeout = False)
1100 ip = IPAddress(self.text)
1101 return ip.version == 6
1103 Log.w("%s is no valid v6 IP" %self.text)
1107 if not self.isValid():
1108 self._setDefault(force=True)
1109 return self.getText()
1111 def setValue(self, val):
1113 val = val.decode("utf-8")
1114 self.text = IPAddress(val).format()
1116 Log.w("No valid IPv6 Address")
1117 self._setDefault(force=True)
1120 def _setDefault(self, force=False):
1121 if not self.text or force:
1122 self.text = self.default
1127 def onDeselect(self, session):
1128 self.setValue(self.text)
1129 ConfigTextBase.onDeselect(self, session)
1131 class ConfigPassword(ConfigText):
1132 def __init__(self, default = "", fixed_size = False, visible_width = False, censor = "*"):
1133 ConfigText.__init__(self, default = default, fixed_size = fixed_size, visible_width = visible_width, censor_char = censor)
1135 # lets the user select between [min, min+stepwidth, min+(stepwidth*2)..., maxval] with maxval <= max depending
1137 # min, max, stepwidth, default are int values
1138 # wraparound: pressing RIGHT key at max value brings you to min value and vice versa if set to True
1139 class ConfigSelectionNumber(ConfigSelection):
1140 def __init__(self, min, max, stepwidth, default = None, wraparound = False):
1141 self.wraparound = wraparound
1144 default = str(default)
1148 choices.append(str(step))
1151 ConfigSelection.__init__(self, choices, default)
1154 return int(ConfigSelection.getValue(self))
1156 def setValue(self, val):
1157 ConfigSelection.setValue(self, str(val))
1159 def handleKey(self, key):
1160 if not self.wraparound:
1161 if key == KEY_RIGHT:
1162 if len(self.choices) == (self.choices.index(self.value) + 1):
1165 if self.choices.index(self.value) == 0:
1167 ConfigSelection.handleKey(self, key)
1169 class ConfigNumber(ConfigText):
1170 def __init__(self, default = 0):
1171 ConfigText.__init__(self, str(default), fixed_size = False)
1174 return int(self.text)
1176 def setValue(self, val):
1177 self.text = str(val)
1180 value = property(getValue, setValue)
1181 _value = property(getValue, setValue)
1183 def isChanged(self):
1184 sv = self.saved_value
1185 strv = self.tostring(self.value)
1186 if sv is None and strv == self.default:
1191 pos = len(self.text) - self.marked_pos
1192 self.text = self.text.lstrip("0")
1195 if pos > len(self.text):
1198 self.marked_pos = len(self.text) - pos
1200 def handleKey(self, key):
1201 if key in KEY_NUMBERS or key == KEY_ASCII:
1202 if key == KEY_ASCII:
1203 ascii = getPrevAsciiCode()
1204 if not (48 <= ascii <= 57):
1207 ascii = getKeyNumber(key) + 48
1208 newChar = unichr(ascii)
1210 self.deleteAllChars()
1211 self.allmarked = False
1212 self.insertChar(newChar, self.marked_pos, False)
1213 self.marked_pos += 1
1215 ConfigText.handleKey(self, key)
1218 def onSelect(self, session):
1219 self.allmarked = (self.value != "")
1220 self._keyboardMode = eRCInput.getInstance().getKeyboardMode()
1222 class ConfigSearchText(ConfigText):
1223 def __init__(self, default = "", fixed_size = False, visible_width = False, special_chars = ""):
1224 ConfigText.__init__(self, default = default, fixed_size = fixed_size, visible_width = visible_width)
1225 NumericalTextInput.__init__(self, nextFunc = self.nextFunc, handleTimeout = False, search = special_chars)
1227 class ConfigDirectory(ConfigText):
1228 def __init__(self, default="", visible_width=60):
1229 ConfigText.__init__(self, default, fixed_size = True, visible_width = visible_width)
1231 def handleKey(self, key):
1238 return ConfigText.getValue(self)
1240 def setValue(self, val):
1243 ConfigText.setValue(self, val)
1245 def getMulti(self, selected):
1247 return ("mtext"[1-selected:], _("List of Storage Devices"), range(0))
1249 return ConfigText.getMulti(self, selected)
1251 def onSelect(self, session):
1252 self.allmarked = (self.value != "")
1253 self._keyboardMode = eRCInput.getInstance().getKeyboardMode()
1256 class ConfigSlider(ConfigElement):
1257 def __init__(self, default = 0, increment = 1, limits = (0, 100)):
1258 ConfigElement.__init__(self)
1259 self.value = self.default = default
1260 self.min = limits[0]
1261 self.max = limits[1]
1262 self.increment = increment
1264 def checkValues(self):
1265 if self.value < self.min:
1266 self.value = self.min
1268 if self.value > self.max:
1269 self.value = self.max
1271 def handleKey(self, key):
1273 self.value -= self.increment
1274 elif key == KEY_RIGHT:
1275 self.value += self.increment
1276 elif key == KEY_HOME:
1277 self.value = self.min
1278 elif key == KEY_END:
1279 self.value = self.max
1285 return "%d / %d" % (self.value, self.max)
1287 def getMulti(self, selected):
1289 return ("slider", self.value, self.max)
1291 def fromstring(self, value):
1294 # a satlist. in fact, it's a ConfigSelection.
1295 class ConfigSatlist(ConfigSelection):
1296 def __init__(self, list, default = None):
1297 if default is not None:
1298 default = str(default)
1299 ConfigSelection.__init__(self, choices = [(str(orbpos), desc) for (orbpos, desc, flags) in list], default = default)
1301 def getOrbitalPosition(self):
1302 if self.value == "":
1304 return int(self.value)
1306 orbital_position = property(getOrbitalPosition)
1308 class ConfigSet(ConfigElement):
1309 def __init__(self, choices, default = [], resort=True):
1310 ConfigElement.__init__(self)
1311 self.resort = resort
1312 if isinstance(choices, list):
1315 self.choices = choicesList(choices, choicesList.LIST_TYPE_LIST)
1317 assert False, "ConfigSet choices must be a list!"
1323 self.default = default
1324 self.help_window = None
1325 self.value = default[:]
1326 self.onSelectedIndexChanged = []
1328 def toggleChoice(self, choice):
1331 value.remove(choice)
1333 value.append(choice)
1338 def handleKey(self, key):
1339 if key in KEY_NUMBERS + [KEY_DELETE, KEY_BACKSPACE, KEY_OK]:
1341 self.toggleChoice(self.choices[self.pos])
1342 elif key == KEY_LEFT:
1344 self.pos = len(self.choices)-1
1347 elif key == KEY_RIGHT:
1348 if self.pos >= len(self.choices)-1:
1352 elif key in (KEY_HOME, KEY_END):
1354 for fnc in self.onSelectedIndexChanged:
1357 def genString(self, lst):
1360 res += self.description[x]+" "
1364 return self.genString(self.value)
1366 def getMulti(self, selected):
1367 if not selected or self.pos == -1:
1368 return ("text", self.genString(self.value))
1371 ch = self.choices[self.pos]
1372 mem = ch in self.value
1378 val1 = self.genString(tmp[:ind])
1379 val2 = " "+self.genString(tmp[ind+1:])
1381 chstr = " "+self.description[ch]+" "
1383 chstr = "("+self.description[ch]+")"
1384 len_val1 = len(val1)
1385 return ("mtext", val1+chstr+val2, range(len_val1, len_val1 + len(chstr)))
1388 def onSelect(self, session):
1389 if session is not None:
1390 from Screens.ConfigSetHelpDialog import ConfigSetHelpDialog
1391 self.help_window = session.instantiateDialog(ConfigSetHelpDialog, self, zPosition=5000)
1392 self.help_window.neverAnimate()
1393 self.help_window.show()
1395 Log.w("Help dialog skipped - session is None!")
1397 def onDeselect(self, session):
1399 if self.help_window:
1400 session.deleteDialog(self.help_window)
1401 self.help_window = None
1402 ConfigElement.onDeselect(self, session)
1404 def tostring(self, value):
1407 def fromstring(self, val):
1409 return literal_eval(val)
1413 description = property(lambda self: descriptionList(self.choices.choices, choicesList.LIST_TYPE_LIST))
1415 class ConfigLocations(ConfigElement):
1416 def __init__(self, default = [], visible_width = False):
1417 ConfigElement.__init__(self)
1418 self.visible_width = visible_width
1420 self.default = default
1422 self.mountpoints = []
1423 self.value = default[:]
1425 def setValue(self, value):
1426 locations = self.locations
1427 loc = [x[0] for x in locations if x[3]]
1428 add = [x for x in value if not x in loc]
1429 diff = add + [x for x in loc if not x in value]
1430 locations = [x for x in locations if not x[0] in diff] + [[x, self.getMountpoint(x), True, True] for x in add]
1431 locations.sort(key = lambda x: x[0])
1432 self.locations = locations
1436 self.checkChangedMountpoints()
1437 locations = self.locations
1440 return [x[0] for x in locations if x[3]]
1442 value = property(getValue, setValue)
1444 def tostring(self, value):
1447 def fromstring(self, val):
1448 return literal_eval(val)
1451 sv = self.saved_value
1455 tmp = self.fromstring(sv)
1456 locations = [[x, None, False, False] for x in tmp]
1457 self.refreshMountpoints()
1459 if fileExists(x[0]):
1460 x[1] = self.getMountpoint(x[0])
1462 self.locations = locations
1465 locations = self.locations
1466 if self.save_disabled or not locations:
1467 self.saved_value = None
1469 self.saved_value = self.tostring([x[0] for x in locations])
1471 def isChanged(self):
1472 sv = self.saved_value
1473 locations = self.locations
1474 if sv is None and not locations:
1476 return self.tostring([x[0] for x in locations]) != sv
1478 def addedMount(self, mp):
1479 for x in self.locations:
1482 elif x[1] == None and fileExists(x[0]):
1483 x[1] = self.getMountpoint(x[0])
1486 def removedMount(self, mp):
1487 for x in self.locations:
1491 def refreshMountpoints(self):
1492 from Components.Harddisk import harddiskmanager
1493 self.mountpoints = [p.mountpoint for p in harddiskmanager.getMountedPartitions() if p.mountpoint != "/"]
1494 self.mountpoints.sort(key = lambda x: -len(x))
1496 def checkChangedMountpoints(self):
1497 oldmounts = self.mountpoints
1498 self.refreshMountpoints()
1499 newmounts = self.mountpoints
1500 if oldmounts == newmounts:
1503 if not x in newmounts:
1504 self.removedMount(x)
1506 if not x in oldmounts:
1509 def getMountpoint(self, file):
1510 file = os_path.realpath(file)+"/"
1511 for m in self.mountpoints:
1512 if file.startswith(m):
1516 def handleKey(self, key):
1520 self.pos = len(self.value)-1
1521 elif key == KEY_RIGHT:
1523 if self.pos >= len(self.value):
1525 elif key in (KEY_HOME, KEY_END):
1529 return " ".join(self.value)
1531 def getMulti(self, selected):
1533 valstr = " ".join(self.value)
1534 if self.visible_width and len(valstr) > self.visible_width:
1535 return ("text", valstr[0:self.visible_width])
1537 return ("text", valstr)
1543 for val in self.value:
1546 valstr += str(val)+" "
1550 if self.visible_width and len(valstr) > self.visible_width:
1551 if ind1+1 < self.visible_width/2:
1554 off = min(ind1+1-self.visible_width/2, len(valstr)-self.visible_width)
1555 return ("mtext", valstr[off:off+self.visible_width], range(ind1-off,ind2-off))
1557 return ("mtext", valstr, range(ind1,ind2))
1559 def onDeselect(self, session):
1561 ConfigElement.onDeselect(self, session)
1564 class ConfigNothing(ConfigSelection):
1566 ConfigSelection.__init__(self, choices = [("","")])
1568 # until here, 'saved_value' always had to be a *string*.
1569 # now, in ConfigSubsection, and only there, saved_value
1570 # is a dict, essentially forming a tree.
1572 # config.foo.bar=True
1573 # config.foobar=False
1576 # config.saved_value == {"foo": {"bar": "True"}, "foobar": "False"}
1579 class ConfigSubsectionContent(object):
1582 # we store a backup of the loaded configuration
1583 # data in self.stored_values, to be able to deploy
1584 # them when a new config element will be added,
1585 # so non-default values are instantly available
1587 # A list, for example:
1588 # config.dipswitches = ConfigSubList()
1589 # config.dipswitches.append(ConfigYesNo())
1590 # config.dipswitches.append(ConfigYesNo())
1591 # config.dipswitches.append(ConfigYesNo())
1592 class ConfigSubList(list, object):
1595 self.stored_values = {}
1605 def getSavedValue(self):
1607 for i, val in enumerate(self):
1608 sv = val.saved_value
1613 def setSavedValue(self, values):
1614 self.stored_values = dict(values)
1615 for (key, val) in self.stored_values.items():
1616 if int(key) < len(self):
1617 self[int(key)].saved_value = val
1619 saved_value = property(getSavedValue, setSavedValue)
1621 def append(self, item):
1623 list.append(self, item)
1624 if i in self.stored_values:
1625 item.saved_value = self.stored_values[i]
1629 return dict([(str(index), value) for index, value in enumerate(self)])
1631 # same as ConfigSubList, just as a dictionary.
1632 # care must be taken that the 'key' has a proper
1633 # str() method, because it will be used in the config
1635 class ConfigSubDict(dict, object):
1638 self.stored_values = {}
1641 for x in self.values():
1645 for x in self.values():
1648 def getSavedValue(self):
1650 for (key, val) in self.items():
1651 sv = val.saved_value
1656 def setSavedValue(self, values):
1657 self.stored_values = dict(values)
1658 for (key, val) in self.items():
1659 if str(key) in self.stored_values:
1660 val.saved_value = self.stored_values[str(key)]
1662 saved_value = property(getSavedValue, setSavedValue)
1664 def __setitem__(self, key, item):
1665 dict.__setitem__(self, key, item)
1666 if str(key) in self.stored_values:
1667 item.saved_value = self.stored_values[str(key)]
1673 # Like the classes above, just with a more "native"
1676 # some evil stuff must be done to allow instant
1677 # loading of added elements. this is why this class
1680 # we need the 'content' because we overwrite
1682 # If you don't understand this, try adding
1683 # __setattr__ to a usual exisiting class and you will.
1684 class ConfigSubsection(object):
1686 self.__dict__["content"] = ConfigSubsectionContent()
1687 self.content.items = { }
1688 self.content.stored_values = { }
1690 def __setattr__(self, name, value):
1691 if name == "saved_value":
1692 return self.setSavedValue(value)
1693 assert isinstance(value, (ConfigSubsection, ConfigElement, ConfigSubList, ConfigSubDict)), "ConfigSubsections can only store ConfigSubsections, ConfigSubLists, ConfigSubDicts or ConfigElements"
1694 content = self.content
1695 content.items[name] = value
1696 x = content.stored_values.get(name, None)
1698 #print "ok, now we have a new item,", name, "and have the following value for it:", x
1699 value.saved_value = x
1702 def __getattr__(self, name):
1703 return self.content.items[name]
1705 def getSavedValue(self):
1706 res = self.content.stored_values
1707 for (key, val) in self.content.items.items():
1708 sv = val.saved_value
1715 def setSavedValue(self, values, append = False):
1716 values = dict(values)
1719 self.content.stored_values.update(values)
1721 self.content.stored_values = values
1723 self.content.stored_values = values
1724 for (key, val) in self.content.items.items():
1725 value = values.get(key, None)
1726 if value is not None:
1727 if append and isinstance(val, ConfigSubsection):
1728 val.setSavedValue(value, append)
1730 val.saved_value = value
1732 saved_value = property(getSavedValue, setSavedValue)
1735 for x in self.content.items.values():
1739 for x in self.content.items.values():
1743 return self.content.items
1745 # the root config object, which also can "pickle" (=serialize)
1746 # down the whole config tree.
1748 # we try to keep non-existing config entries, to apply them whenever
1749 # a new config entry is added to a subsection
1750 # also, non-existing config entries will be saved, so they won't be
1751 # lost when a config entry disappears.
1752 class Config(ConfigSubsection):
1754 ConfigSubsection.__init__(self)
1756 def pickle_this(self, prefix, topickle, result):
1757 for (key, val) in topickle.items():
1758 name = '.'.join((prefix, key))
1759 if isinstance(val, dict):
1760 self.pickle_this(name, val, result)
1761 elif isinstance(val, tuple):
1762 result += [name, '=', val[0], '\n']
1764 result += [name, '=', val, '\n']
1768 self.pickle_this("config", self.saved_value, result)
1769 return ''.join(result)
1771 def unpickle(self, lines, base_file=True, append = False):
1774 if not l or l[0] == '#':
1779 val = l[n+1:].strip()
1781 names = name.split('.')
1782 # if val.find(' ') != -1:
1783 # val = val[:val.find(' ')]
1787 for n in names[:-1]:
1788 base = base.setdefault(n, {})
1790 base[names[-1]] = val
1792 if not base_file: # not the initial config file..
1793 #update config.x.y.value when exist
1795 configEntry = eval(name)
1796 if configEntry is not None:
1797 configEntry.value = val
1798 except (SyntaxError, KeyError):
1801 # we inherit from ConfigSubsection, so ...
1802 #object.__setattr__(self, "saved_value", tree["config"])
1803 if "config" in tree:
1804 self.setSavedValue(tree["config"], append)
1806 def saveToFile(self, filename):
1807 saveFile(filename, self.pickle())
1809 def loadFromFile(self, filename, base_file=False, append = False):
1810 f = open(filename, "r")
1811 lines_read = f.readlines()
1814 # HACK to migrate changed unicable configuration
1815 for l in lines_read:
1818 x = tmp[0].split('.')
1821 if x[1] == 'Nims' and x[3] == 'advanced' and x[6].startswith('unicable'):
1822 if ent > 8 and x[8] in ('vco', 'scr', 'product'):
1823 if x[8] != 'product':
1827 elif x[6] == 'unicableLnbManufacturer':
1828 x[6] = 'unicableLnb.manufacturer'
1830 elif x[6] == 'unicableMatrixManufacturer':
1831 x[6] = 'unicableMatrix.manufacturer'
1834 tmp[0] = '.'.join(x)
1836 print "UNICABLE MIGRATION", l,
1839 self.unpickle(lines, base_file, append)
1843 config.misc = ConfigSubsection()
1846 CONFIG_FILE = resolveFilename(SCOPE_CONFIG, "settings")
1850 config.loadFromFile(self.CONFIG_FILE, True)
1852 print "unable to load config (%s), assuming defaults..." % str(e)
1855 config.saveToFile(self.CONFIG_FILE)
1857 def __resolveValue(self, pickles, cmap):
1859 if cmap.has_key(key):
1860 if len(pickles) > 1:
1861 return self.__resolveValue(pickles[1:], cmap[key].dict())
1863 return str(cmap[key].value)
1866 def getResolvedKey(self, key):
1867 names = key.split('.')
1869 if names[0] == "config":
1870 ret=self.__resolveValue(names[1:], config.content.items)
1871 if ret and len(ret):
1875 def NoSave(element):
1876 element.disableSave()
1879 configfile = ConfigFile()
1882 def getConfigListEntry(*args):
1883 assert len(args) > 0, "getConfigListEntry needs a minimum of one argument (descr)"
1886 def updateConfigElement(element, newelement):
1887 newelement.value = element.value
1893 #config.bla = ConfigSubsection()
1894 #config.bla.test = ConfigYesNo()
1895 #config.nim = ConfigSubList()
1896 #config.nim.append(ConfigSubsection())
1897 #config.nim[0].bla = ConfigYesNo()
1898 #config.nim.append(ConfigSubsection())
1899 #config.nim[1].bla = ConfigYesNo()
1900 #config.nim[1].blub = ConfigYesNo()
1901 #config.arg = ConfigSubDict()
1902 #config.arg["Hello"] = ConfigYesNo()
1904 #config.arg["Hello"].handleKey(KEY_RIGHT)
1905 #config.arg["Hello"].handleKey(KEY_RIGHT)
1907 ##config.saved_value
1911 #print config.pickle()