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