[FastChannelChange] FCC is available only for dvb services (service type == 1)
[vuplus_dvbapp] / skin.py
diff --git a/skin.py b/skin.py
old mode 100644 (file)
new mode 100755 (executable)
index 8de161d..f763dd0
--- a/skin.py
+++ b/skin.py
@@ -1,19 +1,27 @@
-import xml.dom.minidom
+from Tools.Profile import profile
+profile("LOAD:ElementTree")
+import xml.etree.cElementTree
 from os import path
 
 from os import path
 
+profile("LOAD:enigma_skin")
 from enigma import eSize, ePoint, gFont, eWindow, eLabel, ePixmap, eWindowStyleManager, \
 from enigma import eSize, ePoint, gFont, eWindow, eLabel, ePixmap, eWindowStyleManager, \
-       loadPNG, addFont, gRGB, eWindowStyleSkinned
-
+       addFont, gRGB, eWindowStyleSkinned
 from Components.config import ConfigSubsection, ConfigText, config
 from Components.Converter.Converter import Converter
 from Components.Sources.Source import Source, ObsoleteSource
 from Components.config import ConfigSubsection, ConfigText, config
 from Components.Converter.Converter import Converter
 from Components.Sources.Source import Source, ObsoleteSource
-from Tools.Directories import resolveFilename, SCOPE_SKIN, SCOPE_SKIN_IMAGE, SCOPE_FONTS
+from Tools.Directories import resolveFilename, SCOPE_SKIN, SCOPE_SKIN_IMAGE, SCOPE_FONTS, SCOPE_CURRENT_SKIN, SCOPE_CONFIG, fileExists
 from Tools.Import import my_import
 from Tools.Import import my_import
-
-from Tools.XMLTools import elementsWithTag, mergeText
+from Tools.LoadPixmap import LoadPixmap
 
 colorNames = dict()
 
 
 colorNames = dict()
 
+fonts = {
+       "Body": ("Regular", 18, 22, 16),
+       "ChoiceList": ("Regular", 20, 24, 18),
+}
+
+parameters = {}
+
 def dump(x, i=0):
        print " " * i + str(x)
        try:
 def dump(x, i=0):
        print " " * i + str(x)
        try:
@@ -24,18 +32,18 @@ def dump(x, i=0):
 
 class SkinError(Exception):
        def __init__(self, message):
 
 class SkinError(Exception):
        def __init__(self, message):
-               self.message = message
+               self.msg = message
 
        def __str__(self):
 
        def __str__(self):
-               return self.message
+               return "{%s}: %s" % (config.skin.primary_skin.value, self.msg)
 
 dom_skins = [ ]
 
 
 dom_skins = [ ]
 
-def loadSkin(name):
+def loadSkin(name, scope = SCOPE_SKIN):
        # read the skin
        # read the skin
-       filename = resolveFilename(SCOPE_SKIN, name)
+       filename = resolveFilename(scope, name)
        mpath = path.dirname(filename) + "/"
        mpath = path.dirname(filename) + "/"
-       dom_skins.append((mpath, xml.dom.minidom.parse(filename)))
+       dom_skins.append((mpath, xml.etree.cElementTree.parse(filename).getroot()))
 
 # we do our best to always select the "right" value
 # skins are loaded in order of priority: skin with
 
 # we do our best to always select the "right" value
 # skins are loaded in order of priority: skin with
@@ -51,6 +59,12 @@ def loadSkin(name):
 config.skin = ConfigSubsection()
 config.skin.primary_skin = ConfigText(default = "skin.xml")
 
 config.skin = ConfigSubsection()
 config.skin.primary_skin = ConfigText(default = "skin.xml")
 
+profile("LoadSkin")
+try:
+       loadSkin('skin_user.xml', SCOPE_CONFIG)
+except (SkinError, IOError, AssertionError), err:
+       print "not loading user skin: ", err
+
 try:
        loadSkin(config.skin.primary_skin.value)
 except (SkinError, IOError, AssertionError), err:
 try:
        loadSkin(config.skin.primary_skin.value)
 except (SkinError, IOError, AssertionError), err:
@@ -59,19 +73,69 @@ except (SkinError, IOError, AssertionError), err:
        config.skin.primary_skin.value = 'skin.xml'
        loadSkin('skin.xml')
 
        config.skin.primary_skin.value = 'skin.xml'
        loadSkin('skin.xml')
 
+profile("LoadSkinDefault")
 loadSkin('skin_default.xml')
 loadSkin('skin_default.xml')
+profile("LoadSkinDefaultDone")
+
+def parseCoordinate(str, e, size = 0):
+       str = str.strip()
+       if str == "center":
+               val = (e - size)/2
+       else:
+               sl = len(str)
+               l = 1
+
+               if str[0] is 'e':
+                       val = e
+               elif str[0] is 'c':
+                       val = e/2
+               else:
+                       val = 0;
+                       l = 0
 
 
-def parsePosition(str):
+               if sl - l > 0:
+                       if str[sl-1] is '%':
+                               val += e * int(str[l:sl-1]) / 100
+                       else:
+                               val += int(str[l:sl])
+       if val < 0:
+               val = 0
+       return val
+
+def evalPos(pos, wsize, ssize, scale):
+       if pos == "center":
+               pos = (ssize - wsize) / 2
+       else:
+               pos = int(pos) * scale[0] / scale[1]
+       return int(pos)
+
+def parsePosition(str, scale, desktop = None, size = None):
        x, y = str.split(',')
        x, y = str.split(',')
-       return ePoint(int(x), int(y))
+       
+       wsize = 1, 1
+       ssize = 1, 1
+       if desktop is not None:
+               ssize = desktop.size().width(), desktop.size().height()
+       if size is not None:
+               wsize = size.width(), size.height()
+
+       x = evalPos(x, wsize[0], ssize[0], scale[0])
+       y = evalPos(y, wsize[1], ssize[1], scale[1])
+
+       return ePoint(x, y)
 
 
-def parseSize(str):
+def parseSize(str, scale):
        x, y = str.split(',')
        x, y = str.split(',')
-       return eSize(int(x), int(y))
+       return eSize(int(x) * scale[0][0] / scale[0][1], int(y) * scale[1][0] / scale[1][1])
 
 
-def parseFont(str):
-       name, size = str.split(';')
-       return gFont(name, int(size))
+def parseFont(s, scale):
+       try:
+               f = fonts[s]
+               name = f[0]
+               size = f[1]
+       except:
+               name, size = s.split(';')
+       return gFont(name, int(size) * scale[0][0] / scale[0][1])
 
 def parseColor(str):
        if str[0] != '#':
 
 def parseColor(str):
        if str[0] != '#':
@@ -81,282 +145,589 @@ def parseColor(str):
                        raise SkinError("color '%s' must be #aarrggbb or valid named color" % (str))
        return gRGB(int(str[1:], 0x10))
 
                        raise SkinError("color '%s' must be #aarrggbb or valid named color" % (str))
        return gRGB(int(str[1:], 0x10))
 
-def collectAttributes(skinAttributes, node, skin_path_prefix=None, ignore=[]):
+def collectAttributes(skinAttributes, node, context, skin_path_prefix=None, ignore=[]):
        # walk all attributes
        # walk all attributes
-       for p in range(node.attributes.length):
-               a = node.attributes.item(p)
-               
-               # convert to string (was: unicode)
-               attrib = str(a.name)
-               # TODO: localization? as in e1?
-               value = a.value.encode("utf-8")
-               
-               if attrib in ["pixmap", "pointer", "seek_pointer", "backgroundPixmap", "selectionPixmap"]:
-                       value = resolveFilename(SCOPE_SKIN_IMAGE, value, path_prefix=skin_path_prefix)
-               
+       size = None
+       pos = None
+       for attrib, value in node.items():
                if attrib not in ignore:
                if attrib not in ignore:
-                       skinAttributes.append((attrib, value))
-
-def loadPixmap(path):
-       ptr = loadPNG(path)
+                       if attrib in ("pixmap", "pointer", "seek_pointer", "backgroundPixmap", "selectionPixmap"):
+                               value = resolveFilename(SCOPE_SKIN_IMAGE, value, path_prefix=skin_path_prefix)
+
+                       # Bit of a hack this, really. When a window has a flag (e.g. wfNoBorder)
+                       # it needs to be set at least before the size is set, in order for the
+                       # window dimensions to be calculated correctly in all situations.
+                       # If wfNoBorder is applied after the size has been set, the window will fail to clear the title area.
+                       # Similar situation for a scrollbar in a listbox; when the scrollbar setting is applied after
+                       # the size, a scrollbar will not be shown until the selection moves for the first time
+
+                       if attrib == 'size':
+                               size = value.encode("utf-8")
+                       elif attrib == 'position':
+                               pos = value.encode("utf-8")
+                       else:
+                               skinAttributes.append((attrib, value.encode("utf-8")))
+
+       if pos is not None:
+               pos, size = context.parse(pos, size)
+               skinAttributes.append(('position', pos))
+       if size is not None:
+               skinAttributes.append(('size', size))
+
+def loadPixmap(path, desktop):
+       cached = False
+       option = path.find("#")
+       if option != -1:
+               options = path[option+1:].split(',')
+               path = path[:option]
+               cached = "cached" in options
+       ptr = LoadPixmap(path, desktop, cached)
        if ptr is None:
                raise SkinError("pixmap file %s not found!" % (path))
        return ptr
 
        if ptr is None:
                raise SkinError("pixmap file %s not found!" % (path))
        return ptr
 
-def applySingleAttribute(guiObject, desktop, attrib, value):
-       # and set attributes
-       try:
-               if attrib == 'position':
-                       guiObject.move(parsePosition(value))
-               elif attrib == 'size':
-                       guiObject.resize(parseSize(value))
-               elif attrib == 'title':
-                       guiObject.setTitle(_(value))
-               elif attrib == 'text':
-                       guiObject.setText(_(value))
-               elif attrib == 'font':
-                       guiObject.setFont(parseFont(value))
-               elif attrib == 'zPosition':
-                       guiObject.setZPosition(int(value))
-               elif attrib in ["pixmap", "backgroundPixmap", "selectionPixmap"]:
-                       ptr = loadPixmap(value) # this should already have been filename-resolved.
-                       desktop.makeCompatiblePixmap(ptr)
-                       if attrib == "pixmap":
-                               guiObject.setPixmap(ptr)
-                       elif attrib == "backgroundPixmap":
-                               guiObject.setBackgroundPicture(ptr)
-                       elif attrib == "selectionPixmap":
-                               guiObject.setSelectionPicture(ptr)
-                       # guiObject.setPixmapFromFile(value)
-               elif attrib == "alphatest": # used by ePixmap
-                       guiObject.setAlphatest(
-                               { "on": 1,
-                                 "off": 0,
-                                 "blend": 2,
-                               }[value])
-               elif attrib == "orientation": # used by eSlider
-                       try:
-                               guiObject.setOrientation(
-                                       { "orVertical": guiObject.orVertical,
-                                               "orHorizontal": guiObject.orHorizontal
-                                       }[value])
-                       except KeyError:
-                               print "oprientation must be either orVertical or orHorizontal!"
-               elif attrib == "valign":
+class AttributeParser:
+       def __init__(self, guiObject, desktop, scale = ((1,1),(1,1))):
+               self.guiObject = guiObject
+               self.desktop = desktop
+               self.scale = scale
+       def applyOne(self, attrib, value):
+               try:
+                       getattr(self, attrib)(value)
+               except AttributeError:
+                       print "[Skin] Attribute not implemented:", attrib, "value:", value
+               except SkinError, ex:
+                       print "[Skin] Error:", ex
+       def applyAll(self, attrs):
+               for attrib, value in attrs:
                        try:
                        try:
-                               guiObject.setVAlign(
-                                       { "top": guiObject.alignTop,
-                                               "center": guiObject.alignCenter,
-                                               "bottom": guiObject.alignBottom
-                                       }[value])
-                       except KeyError:
-                               print "valign must be either top, center or bottom!"
-               elif attrib == "halign":
+                               getattr(self, attrib)(value)
+                       except AttributeError:
+                               print "[Skin] Attribute not implemented:", attrib, "value:", value
+                       except SkinError, ex:
+                               print "[Skin] Error:", ex
+       def position(self, value):
+               if isinstance(value, tuple):
+                       self.guiObject.move(ePoint(*value))
+               else:
+                       self.guiObject.move(parsePosition(value, self.scale, self.desktop, self.guiObject.csize()))
+       def size(self, value):
+               if isinstance(value, tuple):
+                       self.guiObject.resize(eSize(*value))
+               else:
+                       self.guiObject.resize(parseSize(value, self.scale))
+       def animationPaused(self, value):
+               pass
+       def animationPaused(self, value):
+               self.guiObject.setAnimationMode(
+                       { "disable": 0x00,
+                               "off": 0x00,
+                               "offshow": 0x10,
+                               "offhide": 0x01,
+                               "onshow": 0x01,
+                               "onhide": 0x10,
+                       }[value])
+       def title(self, value):
+               self.guiObject.setTitle(_(value))
+       def text(self, value):
+               self.guiObject.setText(_(value))
+       def font(self, value):
+               self.guiObject.setFont(parseFont(value, self.scale))
+       def zPosition(self, value):
+               self.guiObject.setZPosition(int(value))
+       def itemHeight(self, value):
+               self.guiObject.setItemHeight(int(value))
+       def pixmap(self, value):
+               ptr = loadPixmap(value, self.desktop)
+               self.guiObject.setPixmap(ptr)
+       def backgroundPixmap(self, value):
+               ptr = loadPixmap(value, self.desktop)
+               self.guiObject.setBackgroundPicture(ptr)
+       def selectionPixmap(self, value):
+               ptr = loadPixmap(value, self.desktop)
+               self.guiObject.setSelectionPicture(ptr)
+       def itemHeight(self, value):
+               self.guiObject.setItemHeight(int(value))
+       def alphatest(self, value):
+               self.guiObject.setAlphatest(
+                       { "on": 1,
+                         "off": 0,
+                         "blend": 2,
+                       }[value])
+       def scale(self, value):
+               self.guiObject.setScale(1)
+       def orientation(self, value):
+               try:
+                       self.guiObject.setOrientation(*
+                               { "orVertical": (self.guiObject.orVertical, False),
+                                       "orTopToBottom": (self.guiObject.orVertical, False),
+                                       "orBottomToTop": (self.guiObject.orVertical, True),
+                                       "orHorizontal": (self.guiObject.orHorizontal, False),
+                                       "orLeftToRight": (self.guiObject.orHorizontal, False),
+                                       "orRightToLeft": (self.guiObject.orHorizontal, True),
+                               }[value])
+               except KeyError:
+                       print "oprientation must be either orVertical or orHorizontal!"
+       def valign(self, value):
+               try:
+                       self.guiObject.setVAlign(
+                               { "top": self.guiObject.alignTop,
+                                       "center": self.guiObject.alignCenter,
+                                       "bottom": self.guiObject.alignBottom
+                               }[value])
+               except KeyError:
+                       print "valign must be either top, center or bottom!"
+       def halign(self, value):
+               try:
+                       self.guiObject.setHAlign(
+                               { "left": self.guiObject.alignLeft,
+                                       "center": self.guiObject.alignCenter,
+                                       "right": self.guiObject.alignRight,
+                                       "block": self.guiObject.alignBlock
+                               }[value])
+               except KeyError:
+                       print "halign must be either left, center, right or block!"
+       def flags(self, value):
+               flags = value.split(',')
+               for f in flags:
                        try:
                        try:
-                               guiObject.setHAlign(
-                                       { "left": guiObject.alignLeft,
-                                               "center": guiObject.alignCenter,
-                                               "right": guiObject.alignRight,
-                                               "block": guiObject.alignBlock
-                                       }[value])
+                               fv = eWindow.__dict__[f]
+                               self.guiObject.setFlag(fv)
                        except KeyError:
                        except KeyError:
-                               print "halign must be either left, center, right or block!"
-               elif attrib == "flags":
-                       flags = value.split(',')
-                       for f in flags:
-                               try:
-                                       fv = eWindow.__dict__[f]
-                                       guiObject.setFlag(fv)
-                               except KeyError:
-                                       print "illegal flag %s!" % f
-               elif attrib == "backgroundColor":
-                       guiObject.setBackgroundColor(parseColor(value))
-               elif attrib == "backgroundColorSelected":
-                       guiObject.setBackgroundColorSelected(parseColor(value))
-               elif attrib == "foregroundColor":
-                       guiObject.setForegroundColor(parseColor(value))
-               elif attrib == "foregroundColorSelected":
-                       guiObject.setForegroundColorSelected(parseColor(value))
-               elif attrib == "shadowColor":
-                       guiObject.setShadowColor(parseColor(value))
-               elif attrib == "selectionDisabled":
-                       guiObject.setSelectionEnable(0)
-               elif attrib == "transparent":
-                       guiObject.setTransparent(int(value))
-               elif attrib == "borderColor":
-                       guiObject.setBorderColor(parseColor(value))
-               elif attrib == "borderWidth":
-                       guiObject.setBorderWidth(int(value))
-               elif attrib == "scrollbarMode":
-                       guiObject.setScrollbarMode(
-                               { "showOnDemand": guiObject.showOnDemand,
-                                       "showAlways": guiObject.showAlways,
-                                       "showNever": guiObject.showNever
-                               }[value])
-               elif attrib == "enableWrapAround":
-                       guiObject.setWrapAround(True)
-               elif attrib == "pointer" or attrib == "seek_pointer":
-                       (name, pos) = value.split(':')
-                       pos = parsePosition(pos)
-                       ptr = loadPixmap(name)
-                       desktop.makeCompatiblePixmap(ptr)
-                       guiObject.setPointer({"pointer": 0, "seek_pointer": 1}[attrib], ptr, pos)
-               elif attrib == 'shadowOffset':
-                       guiObject.setShadowOffset(parsePosition(value))
-               elif attrib == 'noWrap':
-                       guiObject.setNoWrap(1)
-               else:
-                       raise SkinError("unsupported attribute " + attrib + "=" + value)
-       except int:
-# AttributeError:
-               print "widget %s (%s) doesn't support attribute %s!" % ("", guiObject.__class__.__name__, attrib)
-
-def applyAllAttributes(guiObject, desktop, attributes):
-       for (attrib, value) in attributes:
-               applySingleAttribute(guiObject, desktop, attrib, value)
-
-def loadSingleSkinData(desktop, dom_skin, path_prefix):
+                               print "illegal flag %s!" % f
+       def backgroundColor(self, value):
+               self.guiObject.setBackgroundColor(parseColor(value))
+       def backgroundColorSelected(self, value):
+               self.guiObject.setBackgroundColorSelected(parseColor(value))
+       def foregroundColor(self, value):
+               self.guiObject.setForegroundColor(parseColor(value))
+       def foregroundColorSelected(self, value):
+               self.guiObject.setForegroundColorSelected(parseColor(value))
+       def shadowColor(self, value):
+               self.guiObject.setShadowColor(parseColor(value))
+       def selectionDisabled(self, value):
+               self.guiObject.setSelectionEnable(0)
+       def transparent(self, value):
+               self.guiObject.setTransparent(int(value))
+       def borderColor(self, value):
+               self.guiObject.setBorderColor(parseColor(value))
+       def borderWidth(self, value):
+               self.guiObject.setBorderWidth(int(value))
+       def scrollbarMode(self, value):
+               self.guiObject.setScrollbarMode(
+                       { "showOnDemand": self.guiObject.showOnDemand,
+                               "showAlways": self.guiObject.showAlways,
+                               "showNever": self.guiObject.showNever
+                       }[value])
+       def enableWrapAround(self, value):
+               self.guiObject.setWrapAround(True)
+       def pointer(self, value):
+               (name, pos) = value.split(':')
+               pos = parsePosition(pos, self.scale)
+               ptr = loadPixmap(name, self.desktop)
+               self.guiObject.setPointer(0, ptr, pos)
+       def seek_pointer(self, value):
+               (name, pos) = value.split(':')
+               pos = parsePosition(pos, self.scale)
+               ptr = loadPixmap(name, self.desktop)
+               self.guiObject.setPointer(1, ptr, pos)
+       def shadowOffset(self, value):
+               self.guiObject.setShadowOffset(parsePosition(value, self.scale))
+       def noWrap(self, value):
+               self.guiObject.setNoWrap(1)
+       def id(self, value):
+               pass
+
+def applySingleAttribute(guiObject, desktop, attrib, value, scale = ((1,1),(1,1))):
+       # Someone still using applySingleAttribute?
+       AttributeParser(guiObject, desktop, scale).applyOne(attrib, value)
+
+def applyAllAttributes(guiObject, desktop, attributes, scale):
+       AttributeParser(guiObject, desktop, scale).applyAll(attributes)
+
+def loadSingleSkinData(desktop, skin, path_prefix):
        """loads skin data like colors, windowstyle etc."""
        """loads skin data like colors, windowstyle etc."""
-       
-       skin = dom_skin.childNodes[0]
-       assert skin.tagName == "skin", "root element in skin must be 'skin'!"
+       assert skin.tag == "skin", "root element in skin must be 'skin'!"
 
 
-       for c in elementsWithTag(skin.childNodes, "output"):
-               id = int(c.getAttribute("id") or "0")
-               if id == 0: # framebuffer
-                       for res in elementsWithTag(c.childNodes, "resolution"):
-                               xres = int(res.getAttribute("xres" or "720"))
-                               yres = int(res.getAttribute("yres" or "576"))
-                               bpp = int(res.getAttribute("bpp" or "32"))
-
-                               from enigma import gFBDC
-                               i = gFBDC.getInstance()
-                               i.setResolution(xres, yres)
+       #print "***SKIN: ", path_prefix
 
 
+       for c in skin.findall("output"):
+               id = c.attrib.get('id')
+               if id:
+                       id = int(id)
+               else:
+                       id = 0
+               if id == 0: # framebuffer
+                       for res in c.findall("resolution"):
+                               get_attr = res.attrib.get
+                               xres = get_attr("xres")
+                               if xres:
+                                       xres = int(xres)
+                               else:
+                                       xres = 720
+                               yres = get_attr("yres")
+                               if yres:
+                                       yres = int(yres)
+                               else:
+                                       yres = 576
+                               bpp = get_attr("bpp")
+                               if bpp:
+                                       bpp = int(bpp)
+                               else:
+                                       bpp = 32
+                               #print "Resolution:", xres,yres,bpp
+                               from enigma import gMainDC
+                               gMainDC.getInstance().setResolution(xres, yres)
+                               desktop.resize(eSize(xres, yres))
                                if bpp != 32:
                                        # load palette (not yet implemented)
                                        pass
 
                                if bpp != 32:
                                        # load palette (not yet implemented)
                                        pass
 
-       for c in elementsWithTag(skin.childNodes, "colors"):
-               for color in elementsWithTag(c.childNodes, "color"):
-                       name = str(color.getAttribute("name"))
-                       color = str(color.getAttribute("value"))
-                       
-                       if not len(color):
-                               raise ("need color and name, got %s %s" % (name, color))
-                               
-                       colorNames[name] = parseColor(color)
-       
-       for c in elementsWithTag(skin.childNodes, "fonts"):
-               for font in elementsWithTag(c.childNodes, "font"):
-                       filename = str(font.getAttribute("filename") or "<NONAME>")
-                       name = str(font.getAttribute("name") or "Regular")
-                       scale = int(font.getAttribute("scale") or "100")
-                       is_replacement = font.getAttribute("replacement") != ""
-                       addFont(resolveFilename(SCOPE_FONTS, filename, path_prefix=path_prefix), name, scale, is_replacement)
-       
-       for windowstyle in elementsWithTag(skin.childNodes, "windowstyle"):
+       for c in skin.findall("colors"):
+               for color in c.findall("color"):
+                       get_attr = color.attrib.get
+                       name = get_attr("name")
+                       color = get_attr("value")
+                       if name and color:
+                               colorNames[name] = parseColor(color)
+                               #print "Color:", name, color
+                       else:
+                               raise SkinError("need color and name, got %s %s" % (name, color))
+
+       for c in skin.findall("fonts"):
+               for font in c.findall("font"):
+                       get_attr = font.attrib.get
+                       filename = get_attr("filename", "<NONAME>")
+                       name = get_attr("name", "Regular")
+                       scale = get_attr("scale")
+                       if scale:
+                               scale = int(scale)
+                       else:
+                               scale = 100
+                       is_replacement = get_attr("replacement") and True or False
+                       resolved_font = resolveFilename(SCOPE_FONTS, filename, path_prefix=path_prefix)
+                       if not fileExists(resolved_font): #when font is not available look at current skin path
+                               skin_path = resolveFilename(SCOPE_CURRENT_SKIN, filename)
+                               if fileExists(skin_path):
+                                       resolved_font = skin_path
+                       addFont(resolved_font, name, scale, is_replacement)
+                       #print "Font: ", resolved_font, name, scale, is_replacement
+
+               for alias in c.findall("alias"):
+                       get = alias.attrib.get
+                       try:
+                               name = get("name")
+                               font = get("font")
+                               size = int(get("size"))
+                               height = int(get("height", size)) # to be calculated some day
+                               width = int(get("width", size))
+                               global fonts
+                               fonts[name] = (font, size, height, width)
+                       except Exception, ex:
+                               print "[SKIN] bad font alias", ex
+
+       for c in skin.findall("parameters"):
+               for parameter in c.findall("parameter"):
+                       get = parameter.attrib.get
+                       try:
+                               name = get("name")
+                               value = get("value")
+                               parameters[name] = map(int, value.split(","))
+                       except Exception, ex:
+                               print "[SKIN] bad parameter", ex
+
+       for c in skin.findall("subtitles"):
+               from enigma import eWidget, eSubtitleWidget
+               scale = ((1,1),(1,1))
+               for substyle in c.findall("sub"):
+                       get_attr = substyle.attrib.get
+                       font = parseFont(get_attr("font"), scale)
+                       col = get_attr("foregroundColor")
+                       if col:
+                               foregroundColor = parseColor(col)
+                               haveColor = 1
+                       else:
+                               foregroundColor = gRGB(0xFFFFFF)
+                               haveColor = 0
+                       col = get_attr("shadowColor")
+                       if col:
+                               shadowColor = parseColor(col)
+                       else:
+                               shadowColor = gRGB(0)
+                       shadowOffset = parsePosition(get_attr("shadowOffset"), scale)
+                       face = eSubtitleWidget.__dict__[get_attr("name")]
+                       eSubtitleWidget.setFontStyle(face, font, haveColor, foregroundColor, shadowColor, shadowOffset)
+
+       for windowstyle in skin.findall("windowstyle"):
                style = eWindowStyleSkinned()
                style = eWindowStyleSkinned()
-               id = int(windowstyle.getAttribute("id") or "0")
-               
+               id = windowstyle.attrib.get("id")
+               if id:
+                       id = int(id)
+               else:
+                       id = 0
+               #print "windowstyle:", id
+
                # defaults
                font = gFont("Regular", 20)
                offset = eSize(20, 5)
                # defaults
                font = gFont("Regular", 20)
                offset = eSize(20, 5)
-               
-               for title in elementsWithTag(windowstyle.childNodes, "title"):
-                       offset = parseSize(title.getAttribute("offset"))
-                       font = parseFont(str(title.getAttribute("font")))
+
+               for title in windowstyle.findall("title"):
+                       get_attr = title.attrib.get
+                       offset = parseSize(get_attr("offset"), ((1,1),(1,1)))
+                       font = parseFont(get_attr("font"), ((1,1),(1,1)))
 
                style.setTitleFont(font);
                style.setTitleOffset(offset)
 
                style.setTitleFont(font);
                style.setTitleOffset(offset)
-               
-               for borderset in elementsWithTag(windowstyle.childNodes, "borderset"):
-                       bsName = str(borderset.getAttribute("name"))
-                       for pixmap in elementsWithTag(borderset.childNodes, "pixmap"):
-                               bpName = str(pixmap.getAttribute("pos"))
-                               filename = str(pixmap.getAttribute("filename"))
-                               
-                               png = loadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix))
-                               
-                               # adapt palette
-                               desktop.makeCompatiblePixmap(png)
-                               style.setPixmap(eWindowStyleSkinned.__dict__[bsName], eWindowStyleSkinned.__dict__[bpName], png)
-
-               for color in elementsWithTag(windowstyle.childNodes, "color"):
-                       type = str(color.getAttribute("name"))
-                       color = parseColor(color.getAttribute("color"))
-                       
+               #print "  ", font, offset
+
+               for borderset in windowstyle.findall("borderset"):
+                       bsName = str(borderset.attrib.get("name"))
+                       for pixmap in borderset.findall("pixmap"):
+                               get_attr = pixmap.attrib.get
+                               bpName = get_attr("pos")
+                               filename = get_attr("filename")
+                               if filename and bpName:
+                                       png = loadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix), desktop)
+                                       style.setPixmap(eWindowStyleSkinned.__dict__[bsName], eWindowStyleSkinned.__dict__[bpName], png)
+                               #print "  borderset:", bpName, filename
+
+               for color in windowstyle.findall("color"):
+                       get_attr = color.attrib.get
+                       colorType = get_attr("name")
+                       color = parseColor(get_attr("color"))
                        try:
                        try:
-                               style.setColor(eWindowStyleSkinned.__dict__["col" + type], color)
+                               style.setColor(eWindowStyleSkinned.__dict__["col" + colorType], color)
                        except:
                        except:
-                               raise ("Unknown color %s" % (type))
-                       
+                               raise SkinError("Unknown color %s" % (colorType))
+                               #pass
+
+                       #print "  color:", type, color
+
                x = eWindowStyleManager.getInstance()
                x.setStyle(id, style)
 
                x = eWindowStyleManager.getInstance()
                x.setStyle(id, style)
 
+display_skin_id = 1
+dom_screens = {}
+
 def loadSkinData(desktop):
 def loadSkinData(desktop):
+       global dom_skins, dom_screens, display_skin_id
        skins = dom_skins[:]
        skins.reverse()
        for (path, dom_skin) in skins:
                loadSingleSkinData(desktop, dom_skin, path)
 
        skins = dom_skins[:]
        skins.reverse()
        for (path, dom_skin) in skins:
                loadSingleSkinData(desktop, dom_skin, path)
 
-def lookupScreen(name):
-       for (path, dom_skin) in dom_skins:
-               # first, find the corresponding screen element
-               skin = dom_skin.childNodes[0] 
-               for x in elementsWithTag(skin.childNodes, "screen"):
-                       if x.getAttribute('name') == name:
-                               return x, path
+               for elem in dom_skin:
+                       if elem.tag == 'screen':
+                               name = elem.attrib.get('name', None)
+                               if name:
+                                       sid = elem.attrib.get('id', None)
+                                       if sid and (int(sid) != display_skin_id):
+                                               # not for this display
+                                               elem.clear()
+                                               continue
+                                       if name in dom_screens:
+                                               # Kill old versions, save memory
+                                               dom_screens[name][0].clear()
+                                       dom_screens[name] = (elem, path)
+                               else:
+                                       # without name, it's useless!
+                                       elem.clear()
+                       else:
+                               # non-screen element, no need for it any longer
+                               elem.clear()
+       # no longer needed, we know where the screens are now.
+       del dom_skins
+
+def lookupScreen(name, style_id):
+       if dom_screens.has_key(name):
+               elem, path = dom_screens[name]
+               screen_style_id = elem.attrib.get('id', '-1')
+               if screen_style_id == '-1' and name.find('ummary') > 0:
+                       screen_style_id = '1'
+               if (style_id != 2 and int(screen_style_id) == -1) or int(screen_style_id) == style_id:
+                       return elem, path
+
        return None, None
 
        return None, None
 
+class additionalWidget:
+       pass
+
+# Class that makes a tuple look like something else. Some plugins just assume
+# that size is a string and try to parse it. This class makes that work.
+class SizeTuple(tuple):
+       def split(self, *args):
+               return (str(self[0]), str(self[1]))
+       def strip(self, *args):
+               return '%s,%s' % self
+       def __str__(self):
+               return '%s,%s' % self
+
+class SkinContext:
+       def __init__(self, parent=None, pos=None, size=None):
+               if parent is not None:
+                       if pos is not None:
+                               pos, size = parent.parse(pos, size)
+                               self.x, self.y = pos
+                               self.w, self.h = size
+                       else:
+                               self.x = None
+                               self.y = None
+                               self.w = None
+                               self.h = None
+       def __str__(self):
+               return "Context (%s,%s)+(%s,%s) " % (self.x, self.y, self.w, self.h)
+       def parse(self, pos, size):
+               if pos == "fill":
+                       pos = (self.x, self.y)
+                       size = (self.w, self.h)
+                       self.w = 0
+                       self.h = 0
+               elif pos == "bottom":
+                       w,h = size.split(',')
+                       h = int(h)
+                       pos = (self.x, self.y + self.h - h)
+                       size = (self.w, h)
+                       self.h -= h
+               elif pos == "top":
+                       w,h = size.split(',')
+                       h = int(h)
+                       pos = (self.x, self.y)
+                       size = (self.w, h)
+                       self.h -= h
+                       self.y += h
+               elif pos == "left":
+                       w,h = size.split(',')
+                       w = int(w)
+                       pos = (self.x, self.y)
+                       size = (w, self.h)
+                       self.x += w
+                       self.w -= w
+               elif pos == "right":
+                       w,h = size.split(',')
+                       w = int(w)
+                       pos = (self.x + self.w - w, self.y)
+                       size = (w, self.h)
+                       self.w -= w
+               else:
+                       size = size.split(',')
+                       size = (parseCoordinate(size[0], self.w), parseCoordinate(size[1], self.h)) 
+                       pos = pos.split(',')
+                       pos = (self.x + parseCoordinate(pos[0], self.w, size[0]), self.y + parseCoordinate(pos[1], self.h, size[1]))            
+               return (SizeTuple(pos), SizeTuple(size))
+
+class SkinContextStack(SkinContext):
+       # A context that stacks things instead of aligning them
+       def parse(self, pos, size):
+               if pos == "fill":
+                       pos = (self.x, self.y)
+                       size = (self.w, self.h)
+               elif pos == "bottom":
+                       w,h = size.split(',')
+                       h = int(h)
+                       pos = (self.x, self.y + self.h - h)
+                       size = (self.w, h)
+               elif pos == "top":
+                       w,h = size.split(',')
+                       h = int(h)
+                       pos = (self.x, self.y)
+                       size = (self.w, h)
+               elif pos == "left":
+                       w,h = size.split(',')
+                       w = int(w)
+                       pos = (self.x, self.y)
+                       size = (w, self.h)
+               elif pos == "right":
+                       w,h = size.split(',')
+                       w = int(w)
+                       pos = (self.x + self.w - w, self.y)
+                       size = (w, self.h)
+               else:
+                       size = size.split(',')
+                       size = (parseCoordinate(size[0], self.w), parseCoordinate(size[1], self.h))
+                       pos = pos.split(',')
+                       pos = (self.x + parseCoordinate(pos[0], self.w, size[0]), self.y + parseCoordinate(pos[1], self.h, size[1]))
+               return (SizeTuple(pos), SizeTuple(size))
+
 def readSkin(screen, skin, names, desktop):
        if not isinstance(names, list):
                names = [names]
 
        name = "<embedded-in-'%s'>" % screen.__class__.__name__
 
 def readSkin(screen, skin, names, desktop):
        if not isinstance(names, list):
                names = [names]
 
        name = "<embedded-in-'%s'>" % screen.__class__.__name__
 
+       style_id = desktop.getStyleID();
+
        # try all skins, first existing one have priority
        for n in names:
        # try all skins, first existing one have priority
        for n in names:
-               myscreen, path = lookupScreen(n)
+               myscreen, path = lookupScreen(n, style_id)
                if myscreen is not None:
                        # use this name for debug output
                        name = n
                        break
 
        # otherwise try embedded skin
                if myscreen is not None:
                        # use this name for debug output
                        name = n
                        break
 
        # otherwise try embedded skin
-       myscreen = myscreen or getattr(screen, "parsedSkin", None)
+       if myscreen is None:
+               myscreen = getattr(screen, "parsedSkin", None)
 
        # try uncompiled embedded skin
        if myscreen is None and getattr(screen, "skin", None):
 
        # try uncompiled embedded skin
        if myscreen is None and getattr(screen, "skin", None):
-               myscreen = screen.parsedSkin = xml.dom.minidom.parseString(screen.skin).childNodes[0]
-
-       assert myscreen is not None, "no skin for screen '" + repr(names) + "' found!"
+               print "Looking for embedded skin"
+               skin_tuple = screen.skin
+               if not isinstance(skin_tuple, tuple):
+                       skin_tuple = (skin_tuple,)
+               for sskin in skin_tuple:
+                       parsedSkin = xml.etree.cElementTree.fromstring(sskin)
+                       screen_style_id = parsedSkin.attrib.get('id', '-1')
+                       if (style_id != 2 and int(screen_style_id) == -1) or int(screen_style_id) == style_id:
+                               myscreen = screen.parsedSkin = parsedSkin
+                               break
+
+       #assert myscreen is not None, "no skin for screen '" + repr(names) + "' found!"
+       if myscreen is None:
+               print "No skin to read..."
+               emptySkin = "<screen></screen>"
+               myscreen = screen.parsedSkin = xml.etree.cElementTree.fromstring(emptySkin)
 
        screen.skinAttributes = [ ]
 
        screen.skinAttributes = [ ]
-       
+
        skin_path_prefix = getattr(screen, "skin_path", path)
 
        skin_path_prefix = getattr(screen, "skin_path", path)
 
-       collectAttributes(screen.skinAttributes, myscreen, skin_path_prefix, ignore=["name"])
-       
+#      collectAttributes(screen.skinAttributes, myscreen, skin_path_prefix, ignore=["name"])
+
+       context = SkinContext()
+       s = desktop.size()
+       context.x = 0
+       context.y = 0
+       context.w = s.width()
+       context.h = s.height()
+       del s
+       collectAttributes(screen.skinAttributes, myscreen, context, skin_path_prefix, ignore=("name",))
+       context = SkinContext(context, myscreen.attrib.get('position'), myscreen.attrib.get('size'))
+
+#
+
        screen.additionalWidgets = [ ]
        screen.renderer = [ ]
        screen.additionalWidgets = [ ]
        screen.renderer = [ ]
-       
+
        visited_components = set()
        visited_components = set()
-       
-       # now walk all widgets
-       for widget in elementsWithTag(myscreen.childNodes, "widget"):
-               # ok, we either have 1:1-mapped widgets ('old style'), or 1:n-mapped 
+
+       # now walk all widgets and stuff
+       def process_none(widget, context):
+               pass
+
+       def process_widget(widget, context):
+               get_attr = widget.attrib.get
+               # ok, we either have 1:1-mapped widgets ('old style'), or 1:n-mapped
                # widgets (source->renderer).
 
                # widgets (source->renderer).
 
-               wname = widget.getAttribute('name')
-               wsource = widget.getAttribute('source')
-               
+               wname = get_attr('name')
+               wsource = get_attr('source')
 
                if wname is None and wsource is None:
                        print "widget has no name and no source!"
 
                if wname is None and wsource is None:
                        print "widget has no name and no source!"
-                       continue
-               
+                       return
+
                if wname:
                if wname:
+                       #print "Widget name=", wname
                        visited_components.add(wname)
 
                        # get corresponding 'gui' object
                        visited_components.add(wname)
 
                        # get corresponding 'gui' object
@@ -364,13 +735,15 @@ def readSkin(screen, skin, names, desktop):
                                attributes = screen[wname].skinAttributes = [ ]
                        except:
                                raise SkinError("component with name '" + wname + "' was not found in skin of screen '" + name + "'!")
                                attributes = screen[wname].skinAttributes = [ ]
                        except:
                                raise SkinError("component with name '" + wname + "' was not found in skin of screen '" + name + "'!")
+                               #print "WARNING: component with name '" + wname + "' was not found in skin of screen '" + name + "'!"
 
 #                      assert screen[wname] is not Source
 
                        # and collect attributes for this
 
 #                      assert screen[wname] is not Source
 
                        # and collect attributes for this
-                       collectAttributes(attributes, widget, skin_path_prefix, ignore=['name'])
+                       collectAttributes(attributes, widget, context, skin_path_prefix, ignore=['name'])
                elif wsource:
                        # get corresponding source
                elif wsource:
                        # get corresponding source
+                       #print "Widget source=", wsource
 
                        while True: # until we found a non-obsolete source
 
 
                        while True: # until we found a non-obsolete source
 
@@ -383,8 +756,8 @@ def readSkin(screen, skin, names, desktop):
                                while len(path) > 1:
                                        scr = screen.getRelatedScreen(path[0])
                                        if scr is None:
                                while len(path) > 1:
                                        scr = screen.getRelatedScreen(path[0])
                                        if scr is None:
-                                               print wsource
-                                               print name
+                                               #print wsource
+                                               #print name
                                                raise SkinError("specified related screen '" + wsource + "' was not found in screen '" + name + "'!")
                                        path = path[1:]
 
                                                raise SkinError("specified related screen '" + wsource + "' was not found in screen '" + name + "'!")
                                        path = path[1:]
 
@@ -404,20 +777,25 @@ def readSkin(screen, skin, names, desktop):
 
                        if source is None:
                                raise SkinError("source '" + wsource + "' was not found in screen '" + name + "'!")
 
                        if source is None:
                                raise SkinError("source '" + wsource + "' was not found in screen '" + name + "'!")
-                       
-                       wrender = widget.getAttribute('render')
-                       
+
+                       wrender = get_attr('render')
+
                        if not wrender:
                                raise SkinError("you must define a renderer with render= for source '%s'" % (wsource))
                        if not wrender:
                                raise SkinError("you must define a renderer with render= for source '%s'" % (wsource))
-                       
-                       for converter in elementsWithTag(widget.childNodes, "convert"):
-                               ctype = converter.getAttribute('type')
+
+                       for converter in widget.findall("convert"):
+                               ctype = converter.get('type')
                                assert ctype, "'convert'-tag needs a 'type'-attribute"
                                assert ctype, "'convert'-tag needs a 'type'-attribute"
-                               parms = mergeText(converter.childNodes).strip()
-                               converter_class = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
-                               
+                               #print "Converter:", ctype
+                               try:
+                                       parms = converter.text.strip()
+                               except:
+                                       parms = ""
+                               #print "Params:", parms
+                               converter_class = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
+
                                c = None
                                c = None
-                               
+
                                for i in source.downstream_elements:
                                        if isinstance(i, converter_class) and i.converter_arguments == parms:
                                                c = i
                                for i in source.downstream_elements:
                                        if isinstance(i, converter_class) and i.converter_arguments == parms:
                                                c = i
@@ -428,54 +806,107 @@ def readSkin(screen, skin, names, desktop):
                                        c.connect(source)
                                else:
                                        print "reused converter!"
                                        c.connect(source)
                                else:
                                        print "reused converter!"
-       
+
                                source = c
                                source = c
-                       
-                       renderer_class = my_import('.'.join(["Components", "Renderer", wrender])).__dict__.get(wrender)
-                       
+
+                       renderer_class = my_import('.'.join(("Components", "Renderer", wrender))).__dict__.get(wrender)
+
                        renderer = renderer_class() # instantiate renderer
                        renderer = renderer_class() # instantiate renderer
-                       
+
                        renderer.connect(source) # connect to source
                        attributes = renderer.skinAttributes = [ ]
                        renderer.connect(source) # connect to source
                        attributes = renderer.skinAttributes = [ ]
-                       collectAttributes(attributes, widget, skin_path_prefix, ignore=['render', 'source'])
-                       
+                       collectAttributes(attributes, widget, context, skin_path_prefix, ignore=['render', 'source'])
+
                        screen.renderer.append(renderer)
 
                        screen.renderer.append(renderer)
 
-       from Components.GUIComponent import GUIComponent
-       nonvisited_components = [x for x in set(screen.keys()) - visited_components if isinstance(x, GUIComponent)]
-       
-       assert not nonvisited_components, "the following components in %s don't have a skin entry: %s" % (name, ', '.join(nonvisited_components))
+       def process_applet(widget, context):
+               try:
+                       codeText = widget.text.strip()
+               except:
+                       codeText = ""
 
 
-       # now walk additional objects
-       for widget in elementsWithTag(myscreen.childNodes, lambda x: x != "widget"):
-               if widget.tagName == "applet":
-                       codeText = mergeText(widget.childNodes).strip()
-                       type = widget.getAttribute('type')
+               #print "Found code:"
+               #print codeText
+               widgetType = widget.attrib.get('type')
 
 
-                       code = compile(codeText, "skin applet", "exec")
-                       
-                       if type == "onLayoutFinish":
-                               screen.onLayoutFinish.append(code)
-                       else:
-                               raise SkinError("applet type '%s' unknown!" % type)
-                       
-                       continue
-               
-               class additionalWidget:
-                       pass
-               
-               w = additionalWidget()
-               
-               if widget.tagName == "eLabel":
-                       w.widget = eLabel
-               elif widget.tagName == "ePixmap":
-                       w.widget = ePixmap
+               code = compile(codeText, "skin applet", "exec")
+
+               if widgetType == "onLayoutFinish":
+                       screen.onLayoutFinish.append(code)
+                       #print "onLayoutFinish = ", codeText
                else:
                else:
-                       raise SkinError("unsupported stuff : %s" % widget.tagName)
-               
+                       raise SkinError("applet type '%s' unknown!" % widgetType)
+                       #print "applet type '%s' unknown!" % type
+
+       def process_elabel(widget, context):
+               w = additionalWidget()
+               w.widget = eLabel
+               w.skinAttributes = [ ]
+               collectAttributes(w.skinAttributes, widget, context, skin_path_prefix, ignore=['name'])
+               screen.additionalWidgets.append(w)
+
+       def process_epixmap(widget, context):
+               w = additionalWidget()
+               w.widget = ePixmap
                w.skinAttributes = [ ]
                w.skinAttributes = [ ]
-               collectAttributes(w.skinAttributes, widget, skin_path_prefix, ignore=['name'])
-               
-               # applyAttributes(guiObject, widget, desktop)
-               # guiObject.thisown = 0
+               collectAttributes(w.skinAttributes, widget, context, skin_path_prefix, ignore=['name'])
                screen.additionalWidgets.append(w)
                screen.additionalWidgets.append(w)
+
+       def process_screen(widget, context):
+               for w in widget.findall("widget"):
+                       process_widget(w, context)
+       
+               for w in widget.getchildren():
+                       if w.tag == "widget":
+                               continue
+
+                       p = processors.get(w.tag, process_none)
+                       p(w, context)
+
+       def process_panel(widget, context):
+               n = widget.attrib.get('name')
+               if n:
+                       try:
+                               s = dom_screens.get(n, None)
+                       except KeyError:
+                               print "[SKIN] Unable to find screen '%s' referred in screen '%s'" % (n, name)
+                       else:
+                               process_screen(s[0], context)
+
+               layout = widget.attrib.get('layout')
+               if layout == 'stack':
+                       cc = SkinContextStack
+               else:
+                       cc = SkinContext
+               try:
+                       c = cc(context, widget.attrib.get('position'), widget.attrib.get('size'))
+               except Exception, ex:
+                       raise SkinError("Failed to create skincontext (%s,%s) in %s: %s" % (widget.attrib.get('position'), widget.attrib.get('size'), context, ex) )
+
+               process_screen(widget, c)
+
+       processors = {
+               None: process_none,
+               "widget": process_widget,
+               "applet": process_applet,
+               "eLabel": process_elabel,
+               "ePixmap": process_epixmap,
+               "panel": process_panel
+       }
+
+       try:
+               context.x = 0 # reset offsets, all components are relative to screen
+               context.y = 0 # coordinates.
+               process_screen(myscreen, context)
+       except SkinError, e:
+                       print "[Skin] SKIN ERROR:", e
+
+       from Components.GUIComponent import GUIComponent
+       nonvisited_components = [x for x in set(screen.keys()) - visited_components if isinstance(x, GUIComponent)]
+       assert not nonvisited_components, "the following components in %s don't have a skin entry: %s" % (name, ', '.join(nonvisited_components))
+       # This may look pointless, but it unbinds 'screen' from the nested scope. A better
+       # solution is to avoid the nested scope above and use the context object to pass
+       # things around.
+       screen = None
+       visited_components = None
+