[SystemInfo] Add PVRSupport.
[vuplus_dvbapp] / skin.py
1 from Tools.Profile import profile
2 profile("LOAD:ElementTree")
3 import xml.etree.cElementTree
4 from os import path
5
6 profile("LOAD:enigma_skin")
7 from enigma import eSize, ePoint, gFont, eWindow, eLabel, ePixmap, eWindowStyleManager, \
8         addFont, gRGB, eWindowStyleSkinned
9 from Components.config import ConfigSubsection, ConfigText, config
10 from Components.Converter.Converter import Converter
11 from Components.Sources.Source import Source, ObsoleteSource
12 from Tools.Directories import resolveFilename, SCOPE_SKIN, SCOPE_SKIN_IMAGE, SCOPE_FONTS, SCOPE_CURRENT_SKIN, SCOPE_CONFIG, fileExists
13 from Tools.Import import my_import
14 from Tools.LoadPixmap import LoadPixmap
15
16 colorNames = dict()
17
18 fonts = {
19         "Body": ("Regular", 18, 22, 16),
20         "ChoiceList": ("Regular", 20, 24, 18),
21 }
22
23 parameters = {}
24
25 def dump(x, i=0):
26         print " " * i + str(x)
27         try:
28                 for n in x.childNodes:
29                         dump(n, i + 1)
30         except:
31                 None
32
33 class SkinError(Exception):
34         def __init__(self, message):
35                 self.msg = message
36
37         def __str__(self):
38                 return "{%s}: %s" % (config.skin.primary_skin.value, self.msg)
39
40 dom_skins = [ ]
41
42 def loadSkin(name, scope = SCOPE_SKIN):
43         # read the skin
44         filename = resolveFilename(scope, name)
45         mpath = path.dirname(filename) + "/"
46         dom_skins.append((mpath, xml.etree.cElementTree.parse(filename).getroot()))
47
48 # we do our best to always select the "right" value
49 # skins are loaded in order of priority: skin with
50 # highest priority is loaded last, usually the user-provided
51 # skin.
52
53 # currently, loadSingleSkinData (colors, bordersets etc.)
54 # are applied one-after-each, in order of ascending priority.
55 # the dom_skin will keep all screens in descending priority,
56 # so the first screen found will be used.
57
58 # example: loadSkin("nemesis_greenline/skin.xml")
59 from Components.SystemInfo import SystemInfo
60 DEFAULT_SKIN = SystemInfo.get("DefaultFullHDSkin", False) and "Vu_HD_1080P/skin.xml" or "Vu_HD/skin.xml"
61
62 config.skin = ConfigSubsection()
63 config.skin.primary_skin = ConfigText(default = DEFAULT_SKIN)
64
65 profile("LoadSkin")
66 try:
67         loadSkin('skin_user.xml', SCOPE_CONFIG)
68 except (SkinError, IOError, AssertionError), err:
69         print "not loading user skin: ", err
70
71 try:
72         loadSkin(config.skin.primary_skin.value)
73 except (SkinError, IOError, AssertionError), err:
74         print "SKIN ERROR:", err
75         print "defaulting to standard skin..."
76         config.skin.primary_skin.value = 'skin.xml'
77         loadSkin('skin.xml')
78
79 profile("LoadSkinDefault")
80 loadSkin('skin_default.xml')
81 profile("LoadSkinDefaultDone")
82
83 def parseCoordinate(str, e, size = 0):
84         str = str.strip()
85         if str == "center":
86                 val = (e - size)/2
87         else:
88                 sl = len(str)
89                 l = 1
90
91                 if str[0] is 'e':
92                         val = e
93                 elif str[0] is 'c':
94                         val = e/2
95                 else:
96                         val = 0;
97                         l = 0
98
99                 if sl - l > 0:
100                         if str[sl-1] is '%':
101                                 val += e * int(str[l:sl-1]) / 100
102                         else:
103                                 val += int(str[l:sl])
104         if val < 0:
105                 val = 0
106         return val
107
108 def evalPos(pos, wsize, ssize, scale):
109         if pos == "center":
110                 pos = (ssize - wsize) / 2
111         else:
112                 pos = int(pos) * scale[0] / scale[1]
113         return int(pos)
114
115 def parsePosition(str, scale, desktop = None, size = None):
116         x, y = str.split(',')
117         
118         wsize = 1, 1
119         ssize = 1, 1
120         if desktop is not None:
121                 ssize = desktop.size().width(), desktop.size().height()
122         if size is not None:
123                 wsize = size.width(), size.height()
124
125         x = evalPos(x, wsize[0], ssize[0], scale[0])
126         y = evalPos(y, wsize[1], ssize[1], scale[1])
127
128         return ePoint(x, y)
129
130 def parseSize(str, scale):
131         x, y = str.split(',')
132         return eSize(int(x) * scale[0][0] / scale[0][1], int(y) * scale[1][0] / scale[1][1])
133
134 def parseFont(s, scale):
135         try:
136                 f = fonts[s]
137                 name = f[0]
138                 size = f[1]
139         except:
140                 name, size = s.split(';')
141         return gFont(name, int(size) * scale[0][0] / scale[0][1])
142
143 def parseColor(str):
144         if str[0] != '#':
145                 try:
146                         return colorNames[str]
147                 except:
148                         raise SkinError("color '%s' must be #aarrggbb or valid named color" % (str))
149         return gRGB(int(str[1:], 0x10))
150
151 def collectAttributes(skinAttributes, node, context, skin_path_prefix=None, ignore=[]):
152         # walk all attributes
153         size = None
154         pos = None
155         for attrib, value in node.items():
156                 if attrib not in ignore:
157                         if attrib in ("pixmap", "pointer", "seek_pointer", "backgroundPixmap", "selectionPixmap"):
158                                 value = resolveFilename(SCOPE_SKIN_IMAGE, value, path_prefix=skin_path_prefix)
159
160                         # Bit of a hack this, really. When a window has a flag (e.g. wfNoBorder)
161                         # it needs to be set at least before the size is set, in order for the
162                         # window dimensions to be calculated correctly in all situations.
163                         # If wfNoBorder is applied after the size has been set, the window will fail to clear the title area.
164                         # Similar situation for a scrollbar in a listbox; when the scrollbar setting is applied after
165                         # the size, a scrollbar will not be shown until the selection moves for the first time
166
167                         if attrib == 'size':
168                                 size = value.encode("utf-8")
169                         elif attrib == 'position':
170                                 pos = value.encode("utf-8")
171                         else:
172                                 skinAttributes.append((attrib, value.encode("utf-8")))
173
174         if pos is not None:
175                 pos, size = context.parse(pos, size)
176                 skinAttributes.append(('position', pos))
177         if size is not None:
178                 skinAttributes.append(('size', size))
179
180 def morphRcImagePath(value):
181         if value.startswith('/usr/share/enigma2') and path.basename(value) in ('rc.png', 'rcold.png'):
182                 value = resolveFilename(SCOPE_SKIN, 'rc/' + 'rc_%d.png' % config.misc.rcused.value)
183         return value
184
185 def loadPixmap(path, desktop):
186         cached = False
187         option = path.find("#")
188         if option != -1:
189                 options = path[option+1:].split(',')
190                 path = path[:option]
191                 cached = "cached" in options
192         ptr = LoadPixmap(morphRcImagePath(path), desktop, cached)
193         if ptr is None:
194                 raise SkinError("pixmap file %s not found!" % (path))
195         return ptr
196
197 class AttributeParser:
198         def __init__(self, guiObject, desktop, scale = ((1,1),(1,1))):
199                 self.guiObject = guiObject
200                 self.desktop = desktop
201                 self.scale = scale
202         def applyOne(self, attrib, value):
203                 try:
204                         getattr(self, attrib)(value)
205                 except AttributeError:
206                         print "[Skin] Attribute not implemented:", attrib, "value:", value
207                 except SkinError, ex:
208                         print "[Skin] Error:", ex
209         def applyAll(self, attrs):
210                 for attrib, value in attrs:
211                         try:
212                                 getattr(self, attrib)(value)
213                         except AttributeError:
214                                 print "[Skin] Attribute not implemented:", attrib, "value:", value
215                         except SkinError, ex:
216                                 print "[Skin] Error:", ex
217         def position(self, value):
218                 if isinstance(value, tuple):
219                         self.guiObject.move(ePoint(*value))
220                 else:
221                         self.guiObject.move(parsePosition(value, self.scale, self.desktop, self.guiObject.csize()))
222         def size(self, value):
223                 if isinstance(value, tuple):
224                         self.guiObject.resize(eSize(*value))
225                 else:
226                         self.guiObject.resize(parseSize(value, self.scale))
227         def animationPaused(self, value):
228                 pass
229         def animationPaused(self, value):
230                 self.guiObject.setAnimationMode(
231                         { "disable": 0x00,
232                                 "off": 0x00,
233                                 "offshow": 0x10,
234                                 "offhide": 0x01,
235                                 "onshow": 0x01,
236                                 "onhide": 0x10,
237                         }[value])
238         def title(self, value):
239                 self.guiObject.setTitle(_(value))
240         def text(self, value):
241                 self.guiObject.setText(_(value))
242         def font(self, value):
243                 self.guiObject.setFont(parseFont(value, self.scale))
244         def zPosition(self, value):
245                 self.guiObject.setZPosition(int(value))
246         def itemHeight(self, value):
247                 self.guiObject.setItemHeight(int(value))
248         def pixmap(self, value):
249                 ptr = loadPixmap(value, self.desktop)
250                 self.guiObject.setPixmap(ptr)
251         def backgroundPixmap(self, value):
252                 ptr = loadPixmap(value, self.desktop)
253                 self.guiObject.setBackgroundPicture(ptr)
254         def selectionPixmap(self, value):
255                 ptr = loadPixmap(value, self.desktop)
256                 self.guiObject.setSelectionPicture(ptr)
257         def itemHeight(self, value):
258                 self.guiObject.setItemHeight(int(value))
259         def alphatest(self, value):
260                 self.guiObject.setAlphatest(
261                         { "on": 1,
262                           "off": 0,
263                           "blend": 2,
264                         }[value])
265         def scale(self, value):
266                 self.guiObject.setScale(1)
267         def orientation(self, value):
268                 try:
269                         self.guiObject.setOrientation(*
270                                 { "orVertical": (self.guiObject.orVertical, False),
271                                         "orTopToBottom": (self.guiObject.orVertical, False),
272                                         "orBottomToTop": (self.guiObject.orVertical, True),
273                                         "orHorizontal": (self.guiObject.orHorizontal, False),
274                                         "orLeftToRight": (self.guiObject.orHorizontal, False),
275                                         "orRightToLeft": (self.guiObject.orHorizontal, True),
276                                 }[value])
277                 except KeyError:
278                         print "oprientation must be either orVertical or orHorizontal!"
279         def valign(self, value):
280                 try:
281                         self.guiObject.setVAlign(
282                                 { "top": self.guiObject.alignTop,
283                                         "center": self.guiObject.alignCenter,
284                                         "bottom": self.guiObject.alignBottom
285                                 }[value])
286                 except KeyError:
287                         print "valign must be either top, center or bottom!"
288         def halign(self, value):
289                 try:
290                         self.guiObject.setHAlign(
291                                 { "left": self.guiObject.alignLeft,
292                                         "center": self.guiObject.alignCenter,
293                                         "right": self.guiObject.alignRight,
294                                         "block": self.guiObject.alignBlock
295                                 }[value])
296                 except KeyError:
297                         print "halign must be either left, center, right or block!"
298         def flags(self, value):
299                 flags = value.split(',')
300                 for f in flags:
301                         try:
302                                 fv = eWindow.__dict__[f]
303                                 self.guiObject.setFlag(fv)
304                         except KeyError:
305                                 print "illegal flag %s!" % f
306         def backgroundColor(self, value):
307                 self.guiObject.setBackgroundColor(parseColor(value))
308         def backgroundColorSelected(self, value):
309                 self.guiObject.setBackgroundColorSelected(parseColor(value))
310         def foregroundColor(self, value):
311                 self.guiObject.setForegroundColor(parseColor(value))
312         def foregroundColorSelected(self, value):
313                 self.guiObject.setForegroundColorSelected(parseColor(value))
314         def shadowColor(self, value):
315                 self.guiObject.setShadowColor(parseColor(value))
316         def selectionDisabled(self, value):
317                 self.guiObject.setSelectionEnable(0)
318         def transparent(self, value):
319                 self.guiObject.setTransparent(int(value))
320         def borderColor(self, value):
321                 self.guiObject.setBorderColor(parseColor(value))
322         def borderWidth(self, value):
323                 self.guiObject.setBorderWidth(int(value))
324         def scrollbarMode(self, value):
325                 self.guiObject.setScrollbarMode(
326                         { "showOnDemand": self.guiObject.showOnDemand,
327                                 "showAlways": self.guiObject.showAlways,
328                                 "showNever": self.guiObject.showNever
329                         }[value])
330         def enableWrapAround(self, value):
331                 self.guiObject.setWrapAround(True)
332         def pointer(self, value):
333                 (name, pos) = value.split(':')
334                 pos = parsePosition(pos, self.scale)
335                 ptr = loadPixmap(name, self.desktop)
336                 self.guiObject.setPointer(0, ptr, pos)
337         def seek_pointer(self, value):
338                 (name, pos) = value.split(':')
339                 pos = parsePosition(pos, self.scale)
340                 ptr = loadPixmap(name, self.desktop)
341                 self.guiObject.setPointer(1, ptr, pos)
342         def shadowOffset(self, value):
343                 self.guiObject.setShadowOffset(parsePosition(value, self.scale))
344         def noWrap(self, value):
345                 self.guiObject.setNoWrap(1)
346         def id(self, value):
347                 pass
348
349 def applySingleAttribute(guiObject, desktop, attrib, value, scale = ((1,1),(1,1))):
350         # Someone still using applySingleAttribute?
351         AttributeParser(guiObject, desktop, scale).applyOne(attrib, value)
352
353 def applyAllAttributes(guiObject, desktop, attributes, scale):
354         AttributeParser(guiObject, desktop, scale).applyAll(attributes)
355
356 def loadSingleSkinData(desktop, skin, path_prefix):
357         """loads skin data like colors, windowstyle etc."""
358         assert skin.tag == "skin", "root element in skin must be 'skin'!"
359
360         #print "***SKIN: ", path_prefix
361
362         for c in skin.findall("output"):
363                 id = c.attrib.get('id')
364                 if id:
365                         id = int(id)
366                 else:
367                         id = 0
368                 if id == 0: # framebuffer
369                         for res in c.findall("resolution"):
370                                 get_attr = res.attrib.get
371                                 xres = get_attr("xres")
372                                 if xres:
373                                         xres = int(xres)
374                                 else:
375                                         xres = 720
376                                 yres = get_attr("yres")
377                                 if yres:
378                                         yres = int(yres)
379                                 else:
380                                         yres = 576
381                                 bpp = get_attr("bpp")
382                                 if bpp:
383                                         bpp = int(bpp)
384                                 else:
385                                         bpp = 32
386                                 #print "Resolution:", xres,yres,bpp
387                                 from enigma import gMainDC
388                                 gMainDC.getInstance().setResolution(xres, yres)
389                                 desktop.resize(eSize(xres, yres))
390                                 if bpp != 32:
391                                         # load palette (not yet implemented)
392                                         pass
393
394         for c in skin.findall("colors"):
395                 for color in c.findall("color"):
396                         get_attr = color.attrib.get
397                         name = get_attr("name")
398                         color = get_attr("value")
399                         if name and color:
400                                 colorNames[name] = parseColor(color)
401                                 #print "Color:", name, color
402                         else:
403                                 raise SkinError("need color and name, got %s %s" % (name, color))
404
405         for c in skin.findall("fonts"):
406                 for font in c.findall("font"):
407                         get_attr = font.attrib.get
408                         filename = get_attr("filename", "<NONAME>")
409                         name = get_attr("name", "Regular")
410                         scale = get_attr("scale")
411                         if scale:
412                                 scale = int(scale)
413                         else:
414                                 scale = 100
415                         is_replacement = get_attr("replacement") and True or False
416                         resolved_font = resolveFilename(SCOPE_FONTS, filename, path_prefix=path_prefix)
417                         if not fileExists(resolved_font): #when font is not available look at current skin path
418                                 skin_path = resolveFilename(SCOPE_CURRENT_SKIN, filename)
419                                 if fileExists(skin_path):
420                                         resolved_font = skin_path
421                         addFont(resolved_font, name, scale, is_replacement)
422                         #print "Font: ", resolved_font, name, scale, is_replacement
423
424                 for alias in c.findall("alias"):
425                         get = alias.attrib.get
426                         try:
427                                 name = get("name")
428                                 font = get("font")
429                                 size = int(get("size"))
430                                 height = int(get("height", size)) # to be calculated some day
431                                 width = int(get("width", size))
432                                 global fonts
433                                 fonts[name] = (font, size, height, width)
434                         except Exception, ex:
435                                 print "[SKIN] bad font alias", ex
436
437         for c in skin.findall("parameters"):
438                 for parameter in c.findall("parameter"):
439                         get = parameter.attrib.get
440                         try:
441                                 name = get("name")
442                                 value = get("value")
443                                 parameters[name] = map(int, value.split(","))
444                         except Exception, ex:
445                                 print "[SKIN] bad parameter", ex
446
447         for c in skin.findall("subtitles"):
448                 from enigma import eWidget, eSubtitleWidget
449                 scale = ((1,1),(1,1))
450                 for substyle in c.findall("sub"):
451                         get_attr = substyle.attrib.get
452                         font = parseFont(get_attr("font"), scale)
453                         col = get_attr("foregroundColor")
454                         if col:
455                                 foregroundColor = parseColor(col)
456                                 haveColor = 1
457                         else:
458                                 foregroundColor = gRGB(0xFFFFFF)
459                                 haveColor = 0
460                         col = get_attr("shadowColor")
461                         if col:
462                                 shadowColor = parseColor(col)
463                         else:
464                                 shadowColor = gRGB(0)
465                         shadowOffset = parsePosition(get_attr("shadowOffset"), scale)
466                         face = eSubtitleWidget.__dict__[get_attr("name")]
467                         eSubtitleWidget.setFontStyle(face, font, haveColor, foregroundColor, shadowColor, shadowOffset)
468
469         for windowstyle in skin.findall("windowstyle"):
470                 style = eWindowStyleSkinned()
471                 id = windowstyle.attrib.get("id")
472                 if id:
473                         id = int(id)
474                 else:
475                         id = 0
476                 #print "windowstyle:", id
477
478                 # defaults
479                 font = gFont("Regular", 20)
480                 offset = eSize(20, 5)
481
482                 for title in windowstyle.findall("title"):
483                         get_attr = title.attrib.get
484                         offset = parseSize(get_attr("offset"), ((1,1),(1,1)))
485                         font = parseFont(get_attr("font"), ((1,1),(1,1)))
486
487                 style.setTitleFont(font);
488                 style.setTitleOffset(offset)
489                 #print "  ", font, offset
490
491                 for borderset in windowstyle.findall("borderset"):
492                         bsName = str(borderset.attrib.get("name"))
493                         for pixmap in borderset.findall("pixmap"):
494                                 get_attr = pixmap.attrib.get
495                                 bpName = get_attr("pos")
496                                 filename = get_attr("filename")
497                                 if filename and bpName:
498                                         png = loadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix), desktop)
499                                         style.setPixmap(eWindowStyleSkinned.__dict__[bsName], eWindowStyleSkinned.__dict__[bpName], png)
500                                 #print "  borderset:", bpName, filename
501
502                 for color in windowstyle.findall("color"):
503                         get_attr = color.attrib.get
504                         colorType = get_attr("name")
505                         color = parseColor(get_attr("color"))
506                         try:
507                                 style.setColor(eWindowStyleSkinned.__dict__["col" + colorType], color)
508                         except:
509                                 raise SkinError("Unknown color %s" % (colorType))
510                                 #pass
511
512                         #print "  color:", type, color
513
514                 x = eWindowStyleManager.getInstance()
515                 x.setStyle(id, style)
516
517 display_skin_id = 1
518 dom_screens = {}
519
520 def loadSkinData(desktop):
521         global dom_skins, dom_screens, display_skin_id
522         skins = dom_skins[:]
523         skins.reverse()
524         for (path, dom_skin) in skins:
525                 loadSingleSkinData(desktop, dom_skin, path)
526
527                 for elem in dom_skin:
528                         if elem.tag == 'screen':
529                                 name = elem.attrib.get('name', None)
530                                 if name:
531                                         sid = elem.attrib.get('id', None)
532                                         if sid and (int(sid) != display_skin_id):
533                                                 # not for this display
534                                                 elem.clear()
535                                                 continue
536                                         if name in dom_screens:
537                                                 # Kill old versions, save memory
538                                                 dom_screens[name][0].clear()
539                                         dom_screens[name] = (elem, path)
540                                 else:
541                                         # without name, it's useless!
542                                         elem.clear()
543                         else:
544                                 # non-screen element, no need for it any longer
545                                 elem.clear()
546         # no longer needed, we know where the screens are now.
547         del dom_skins
548
549 def lookupScreen(name, style_id):
550         if dom_screens.has_key(name):
551                 elem, path = dom_screens[name]
552                 screen_style_id = elem.attrib.get('id', '-1')
553                 if screen_style_id == '-1' and name.find('ummary') > 0:
554                         screen_style_id = '1'
555                 if (style_id != 2 and int(screen_style_id) == -1) or int(screen_style_id) == style_id:
556                         return elem, path
557
558         return None, None
559
560 class additionalWidget:
561         pass
562
563 # Class that makes a tuple look like something else. Some plugins just assume
564 # that size is a string and try to parse it. This class makes that work.
565 class SizeTuple(tuple):
566         def split(self, *args):
567                 return (str(self[0]), str(self[1]))
568         def strip(self, *args):
569                 return '%s,%s' % self
570         def __str__(self):
571                 return '%s,%s' % self
572
573 class SkinContext:
574         def __init__(self, parent=None, pos=None, size=None):
575                 if parent is not None:
576                         if pos is not None:
577                                 pos, size = parent.parse(pos, size)
578                                 self.x, self.y = pos
579                                 self.w, self.h = size
580                         else:
581                                 self.x = None
582                                 self.y = None
583                                 self.w = None
584                                 self.h = None
585         def __str__(self):
586                 return "Context (%s,%s)+(%s,%s) " % (self.x, self.y, self.w, self.h)
587         def parse(self, pos, size):
588                 if pos == "fill":
589                         pos = (self.x, self.y)
590                         size = (self.w, self.h)
591                         self.w = 0
592                         self.h = 0
593                 elif pos == "bottom":
594                         w,h = size.split(',')
595                         h = int(h)
596                         pos = (self.x, self.y + self.h - h)
597                         size = (self.w, h)
598                         self.h -= h
599                 elif pos == "top":
600                         w,h = size.split(',')
601                         h = int(h)
602                         pos = (self.x, self.y)
603                         size = (self.w, h)
604                         self.h -= h
605                         self.y += h
606                 elif pos == "left":
607                         w,h = size.split(',')
608                         w = int(w)
609                         pos = (self.x, self.y)
610                         size = (w, self.h)
611                         self.x += w
612                         self.w -= w
613                 elif pos == "right":
614                         w,h = size.split(',')
615                         w = int(w)
616                         pos = (self.x + self.w - w, self.y)
617                         size = (w, self.h)
618                         self.w -= w
619                 else:
620                         size = size.split(',')
621                         size = (parseCoordinate(size[0], self.w), parseCoordinate(size[1], self.h)) 
622                         pos = pos.split(',')
623                         pos = (self.x + parseCoordinate(pos[0], self.w, size[0]), self.y + parseCoordinate(pos[1], self.h, size[1]))            
624                 return (SizeTuple(pos), SizeTuple(size))
625
626 class SkinContextStack(SkinContext):
627         # A context that stacks things instead of aligning them
628         def parse(self, pos, size):
629                 if pos == "fill":
630                         pos = (self.x, self.y)
631                         size = (self.w, self.h)
632                 elif pos == "bottom":
633                         w,h = size.split(',')
634                         h = int(h)
635                         pos = (self.x, self.y + self.h - h)
636                         size = (self.w, h)
637                 elif pos == "top":
638                         w,h = size.split(',')
639                         h = int(h)
640                         pos = (self.x, self.y)
641                         size = (self.w, h)
642                 elif pos == "left":
643                         w,h = size.split(',')
644                         w = int(w)
645                         pos = (self.x, self.y)
646                         size = (w, self.h)
647                 elif pos == "right":
648                         w,h = size.split(',')
649                         w = int(w)
650                         pos = (self.x + self.w - w, self.y)
651                         size = (w, self.h)
652                 else:
653                         size = size.split(',')
654                         size = (parseCoordinate(size[0], self.w), parseCoordinate(size[1], self.h))
655                         pos = pos.split(',')
656                         pos = (self.x + parseCoordinate(pos[0], self.w, size[0]), self.y + parseCoordinate(pos[1], self.h, size[1]))
657                 return (SizeTuple(pos), SizeTuple(size))
658
659 def readSkin(screen, skin, names, desktop):
660         if not isinstance(names, list):
661                 names = [names]
662
663         name = "<embedded-in-'%s'>" % screen.__class__.__name__
664
665         style_id = desktop.getStyleID();
666
667         # try all skins, first existing one have priority
668         for n in names:
669                 myscreen, path = lookupScreen(n, style_id)
670                 if myscreen is not None:
671                         # use this name for debug output
672                         name = n
673                         break
674
675         # otherwise try embedded skin
676         if myscreen is None:
677                 myscreen = getattr(screen, "parsedSkin", None)
678
679         # try uncompiled embedded skin
680         if myscreen is None and getattr(screen, "skin", None):
681                 print "Looking for embedded skin"
682                 skin_tuple = screen.skin
683                 if not isinstance(skin_tuple, tuple):
684                         skin_tuple = (skin_tuple,)
685                 for sskin in skin_tuple:
686                         parsedSkin = xml.etree.cElementTree.fromstring(sskin)
687                         screen_style_id = parsedSkin.attrib.get('id', '-1')
688                         if (style_id != 2 and int(screen_style_id) == -1) or int(screen_style_id) == style_id:
689                                 myscreen = screen.parsedSkin = parsedSkin
690                                 break
691
692         #assert myscreen is not None, "no skin for screen '" + repr(names) + "' found!"
693         if myscreen is None:
694                 print "No skin to read..."
695                 emptySkin = "<screen></screen>"
696                 myscreen = screen.parsedSkin = xml.etree.cElementTree.fromstring(emptySkin)
697
698         screen.skinAttributes = [ ]
699
700         skin_path_prefix = getattr(screen, "skin_path", path)
701
702 #       collectAttributes(screen.skinAttributes, myscreen, skin_path_prefix, ignore=["name"])
703
704         context = SkinContext()
705         s = desktop.size()
706         context.x = 0
707         context.y = 0
708         context.w = s.width()
709         context.h = s.height()
710         del s
711         collectAttributes(screen.skinAttributes, myscreen, context, skin_path_prefix, ignore=("name",))
712         context = SkinContext(context, myscreen.attrib.get('position'), myscreen.attrib.get('size'))
713
714 #
715
716         screen.additionalWidgets = [ ]
717         screen.renderer = [ ]
718
719         visited_components = set()
720
721         # now walk all widgets and stuff
722         def process_none(widget, context):
723                 pass
724
725         def process_widget(widget, context):
726                 get_attr = widget.attrib.get
727                 # ok, we either have 1:1-mapped widgets ('old style'), or 1:n-mapped
728                 # widgets (source->renderer).
729
730                 wname = get_attr('name')
731                 wsource = get_attr('source')
732
733                 if wname is None and wsource is None:
734                         print "widget has no name and no source!"
735                         return
736
737                 if wname:
738                         #print "Widget name=", wname
739                         visited_components.add(wname)
740
741                         # get corresponding 'gui' object
742                         try:
743                                 attributes = screen[wname].skinAttributes = [ ]
744                         except:
745                                 raise SkinError("component with name '" + wname + "' was not found in skin of screen '" + name + "'!")
746                                 #print "WARNING: component with name '" + wname + "' was not found in skin of screen '" + name + "'!"
747
748 #                       assert screen[wname] is not Source
749
750                         # and collect attributes for this
751                         collectAttributes(attributes, widget, context, skin_path_prefix, ignore=['name'])
752                 elif wsource:
753                         # get corresponding source
754                         #print "Widget source=", wsource
755
756                         while True: # until we found a non-obsolete source
757
758                                 # parse our current "wsource", which might specifiy a "related screen" before the dot,
759                                 # for example to reference a parent, global or session-global screen.
760                                 scr = screen
761
762                                 # resolve all path components
763                                 path = wsource.split('.')
764                                 while len(path) > 1:
765                                         scr = screen.getRelatedScreen(path[0])
766                                         if scr is None:
767                                                 #print wsource
768                                                 #print name
769                                                 raise SkinError("specified related screen '" + wsource + "' was not found in screen '" + name + "'!")
770                                         path = path[1:]
771
772                                 # resolve the source.
773                                 source = scr.get(path[0])
774                                 if isinstance(source, ObsoleteSource):
775                                         # however, if we found an "obsolete source", issue warning, and resolve the real source.
776                                         print "WARNING: SKIN '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
777                                         print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
778                                         if source.description:
779                                                 print source.description
780
781                                         wsource = source.new_source
782                                 else:
783                                         # otherwise, use that source.
784                                         break
785
786                         if source is None:
787                                 raise SkinError("source '" + wsource + "' was not found in screen '" + name + "'!")
788
789                         wrender = get_attr('render')
790
791                         if not wrender:
792                                 raise SkinError("you must define a renderer with render= for source '%s'" % (wsource))
793
794                         for converter in widget.findall("convert"):
795                                 ctype = converter.get('type')
796                                 assert ctype, "'convert'-tag needs a 'type'-attribute"
797                                 #print "Converter:", ctype
798                                 try:
799                                         parms = converter.text.strip()
800                                 except:
801                                         parms = ""
802                                 #print "Params:", parms
803                                 converter_class = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
804
805                                 c = None
806
807                                 for i in source.downstream_elements:
808                                         if isinstance(i, converter_class) and i.converter_arguments == parms:
809                                                 c = i
810
811                                 if c is None:
812                                         print "allocating new converter!"
813                                         c = converter_class(parms)
814                                         c.connect(source)
815                                 else:
816                                         print "reused converter!"
817
818                                 source = c
819
820                         renderer_class = my_import('.'.join(("Components", "Renderer", wrender))).__dict__.get(wrender)
821
822                         renderer = renderer_class() # instantiate renderer
823
824                         renderer.connect(source) # connect to source
825                         attributes = renderer.skinAttributes = [ ]
826                         collectAttributes(attributes, widget, context, skin_path_prefix, ignore=['render', 'source'])
827
828                         screen.renderer.append(renderer)
829
830         def process_applet(widget, context):
831                 try:
832                         codeText = widget.text.strip()
833                 except:
834                         codeText = ""
835
836                 #print "Found code:"
837                 #print codeText
838                 widgetType = widget.attrib.get('type')
839
840                 code = compile(codeText, "skin applet", "exec")
841
842                 if widgetType == "onLayoutFinish":
843                         screen.onLayoutFinish.append(code)
844                         #print "onLayoutFinish = ", codeText
845                 else:
846                         raise SkinError("applet type '%s' unknown!" % widgetType)
847                         #print "applet type '%s' unknown!" % type
848
849         def process_elabel(widget, context):
850                 w = additionalWidget()
851                 w.widget = eLabel
852                 w.skinAttributes = [ ]
853                 collectAttributes(w.skinAttributes, widget, context, skin_path_prefix, ignore=['name'])
854                 screen.additionalWidgets.append(w)
855
856         def process_epixmap(widget, context):
857                 w = additionalWidget()
858                 w.widget = ePixmap
859                 w.skinAttributes = [ ]
860                 collectAttributes(w.skinAttributes, widget, context, skin_path_prefix, ignore=['name'])
861                 screen.additionalWidgets.append(w)
862
863         def process_screen(widget, context):
864                 for w in widget.findall("widget"):
865                         process_widget(w, context)
866         
867                 for w in widget.getchildren():
868                         if w.tag == "widget":
869                                 continue
870
871                         p = processors.get(w.tag, process_none)
872                         p(w, context)
873
874         def process_panel(widget, context):
875                 n = widget.attrib.get('name')
876                 if n:
877                         try:
878                                 s = dom_screens.get(n, None)
879                         except KeyError:
880                                 print "[SKIN] Unable to find screen '%s' referred in screen '%s'" % (n, name)
881                         else:
882                                 process_screen(s[0], context)
883
884                 layout = widget.attrib.get('layout')
885                 if layout == 'stack':
886                         cc = SkinContextStack
887                 else:
888                         cc = SkinContext
889                 try:
890                         c = cc(context, widget.attrib.get('position'), widget.attrib.get('size'))
891                 except Exception, ex:
892                         raise SkinError("Failed to create skincontext (%s,%s) in %s: %s" % (widget.attrib.get('position'), widget.attrib.get('size'), context, ex) )
893
894                 process_screen(widget, c)
895
896         processors = {
897                 None: process_none,
898                 "widget": process_widget,
899                 "applet": process_applet,
900                 "eLabel": process_elabel,
901                 "ePixmap": process_epixmap,
902                 "panel": process_panel
903         }
904
905         try:
906                 context.x = 0 # reset offsets, all components are relative to screen
907                 context.y = 0 # coordinates.
908                 process_screen(myscreen, context)
909         except SkinError, e:
910                         print "[Skin] SKIN ERROR:", e
911
912         from Components.GUIComponent import GUIComponent
913         nonvisited_components = [x for x in set(screen.keys()) - visited_components if isinstance(x, GUIComponent)]
914         assert not nonvisited_components, "the following components in %s don't have a skin entry: %s" % (name, ', '.join(nonvisited_components))
915         # This may look pointless, but it unbinds 'screen' from the nested scope. A better
916         # solution is to avoid the nested scope above and use the context object to pass
917         # things around.
918         screen = None
919         visited_components = None
920