4.5.0r6
[enigma2.git] / usr / lib / enigma2 / python / skin.py
1 from __future__ import division
2 from __future__ import print_function
3 from Tools.Profile import profile
4 import six
5 profile("LOAD:ElementTree")
6 import xml.etree.cElementTree
7 from os.path import dirname
8
9 profile("LOAD:enigma_skin")
10 from enigma import eSize, ePoint, gFont, eWindow, eLabel, ePixmap, eWindowStyleManager, \
11         addFont, gRGB, eWindowStyleSkinned, eWindowStyleScrollbar, eListboxPythonStringContent, eListboxPythonConfigContent, eListbox
12 from Components.config import ConfigSubsection, ConfigText, config, ConfigBoolean
13 from Components.Sources.Source import ObsoleteSource
14 from Tools.Directories import resolveFilename, SCOPE_SKIN, SCOPE_SKIN_IMAGE, SCOPE_FONTS, SCOPE_CURRENT_SKIN, SCOPE_CONFIG, fileExists
15 from Tools.Import import my_import
16 from Tools.LoadPixmap import LoadPixmap
17 from Tools.Log import Log
18
19 from compat import skin_applet_compile
20
21 from re import search as re_search
22
23 HAS_SKIN_USER_DISPLAY = True
24
25 colorNames = dict()
26 skinGlobals = dict()
27
28 def dump(x, i=0):
29         print(" " * i + str(x))
30         try:
31                 for n in x.childNodes:
32                         dump(n, i + 1)
33         except:
34                 None
35
36 class SkinError(Exception):
37         def __init__(self, message):
38                 self.msg = message
39
40         def __str__(self):
41                 return "{%s}: %s" % (config.skin.primary_skin.value, self.msg)
42
43 def getSkinYRes(dom):
44         yres = 0
45         for c in dom.findall("output"):
46                 id = c.attrib.get('id')
47                 if id:
48                         id = int(id)
49                 else:
50                         id = 0
51                 if id == 0: # framebuffer
52                         for res in c.findall("resolution"):
53                                 yres = int(res.get("yres", "576"))
54                                 break
55         return yres
56
57 dom_skins = [ ]
58
59 def loadSkin(name, scope = SCOPE_SKIN):
60         # read the skin
61         filename = resolveFilename(scope, name)
62         mpath = dirname(filename) + "/"
63         dom_skins.append((mpath, xml.etree.cElementTree.parse(filename).getroot()))
64
65 # we do our best to always select the "right" value
66 # skins are loaded in order of priority: skin with
67 # highest priority is loaded last, usually the user-provided
68 # skin.
69
70 # currently, loadSingleSkinData (colors, bordersets etc.)
71 # are applied one-after-each, in order of ascending priority.
72 # the dom_skin will keep all screens in descending priority,
73 # so the first screen found will be used.
74
75 # example: loadSkin("nemesis_greenline/skin.xml")
76 config.skin = ConfigSubsection()
77 config.skin.primary_skin = ConfigText(default = "skin.xml")
78
79 profile("LoadSkin")
80 try:
81         loadSkin('skin_user_display.xml', SCOPE_CONFIG)
82 except (SkinError, IOError, AssertionError) as err:
83         print("not loading display user skin: ", err)
84
85 try:
86         loadSkin('skin_user.xml', SCOPE_CONFIG)
87 except (SkinError, IOError, AssertionError) as err:
88         print("not loading user skin: ", err)
89
90 try:
91         loadSkin(config.skin.primary_skin.value)
92 except (SkinError, IOError, AssertionError) as err:
93         print("SKIN ERROR:", err)
94         print("defaulting to standard skin...")
95         config.skin.primary_skin.value = 'skin.xml'
96         loadSkin('skin.xml')
97
98 yres = getSkinYRes(dom_skins[-1][1])
99 Log.i("Skin resultion is %s" %(yres,))
100 if yres > 0:
101         skin_default_specific = 'skin_default_%s.xml' %yres
102         try:
103                 loadSkin(skin_default_specific)
104         except (SkinError, IOError, AssertionError) as err:
105                 Log.w("Not loading %s %s" %(skin_default_specific,err))
106
107 profile("LoadSkinDefault")
108 loadSkin('skin_default.xml')
109 profile("LoadSkinDefaultDone")
110
111
112 def parsePercent(val, base):
113         return int(float(val.replace("%", "")) / 100.0 * base)
114
115 def evalPos(pos, wsize, ssize, scale):
116         if pos == "center":
117                 pos = (ssize - wsize) // 2
118         elif pos == "max":
119                 pos = ssize - wsize
120         elif pos.endswith("%"):
121                 pos = parsePercent(pos, ssize)
122         else:
123                 pos = int(pos) * scale[0] // scale[1]
124         return int(pos)
125
126 def getParentSize(desktop, guiObject):
127         parent_size = None
128         if guiObject is not None:
129                 parent_size = guiObject.parentCsize()
130                 if parent_size.isEmpty() and desktop is not None:
131                         parent_size = desktop.size()
132         else:
133                 parent_size = desktop.size()
134         return parent_size
135
136 def translateVariable(value):
137         match = re_search("{{(.*)}}", value)
138         if match and len(match.groups()) == 1:
139                 key = match.group(1)
140                 val = skinGlobals.get(key, value)
141                 Log.i("%s => %s" %(key, val))
142                 return val
143         return value
144
145 def parsePosition(value, scale, desktop = None, guiObject = None):
146         value = translateVariable(value)
147         p = value.split(',')
148         x, y = p[0], p[1]
149         wsize = 1, 1
150         ssize = 1, 1
151
152         size = parent_size = None
153         if guiObject:
154                 size = guiObject.csize()
155                 parent_size = getParentSize(desktop, guiObject)
156
157         if parent_size is not None and not parent_size.isEmpty():
158                 ssize = parent_size.width(), parent_size.height()
159         if size is not None:
160                 wsize = size.width(), size.height()
161
162         x = evalPos(x, wsize[0], ssize[0], scale[0])
163         y = evalPos(y, wsize[1], ssize[1], scale[1])
164
165         if len(p) == 4:
166                 x = x + int(p[2])
167                 y = y + int(p[3])
168
169         return ePoint(x, y)
170
171 def parseSize(value, scale, desktop = None, guiObject = None):
172         value = translateVariable(value)
173         x, y = value.split(',')
174         if guiObject is not None:
175                 parent_size = getParentSize(desktop, guiObject)
176                 #width aliases
177                 if x.endswith("%"):
178                         x = parsePercent(x, parent_size.width())
179                 elif x == "fill_parent":
180                         x = parent_size.width()
181                 #height aliases
182                 if y.endswith("%"):
183                         y = parsePercent(y, parent_size.height())
184                 elif y == "fill_parent":
185                         y = parent_size.height()
186         return eSize(int(x) * scale[0][0] // scale[0][1], int(y) * scale[1][0] // scale[1][1])
187
188 def parseFont(str, scale):
189         name, size = str.split(';')
190         return gFont(name, int(size) * scale[0][0] // scale[0][1])
191
192 def parseColor(str):
193         if str[0] != '#':
194                 try:
195                         return colorNames[str]
196                 except:
197                         raise SkinError("color '%s' must be #aarrggbb or valid named color" % (str))
198         return gRGB(int(str[1:], 0x10))
199
200 def parseValue(str):
201         try:
202                 return int(str)
203         except:
204                 raise SkinError("value '%s' is not integer" % (str))
205
206 def collectAttributes(skinAttributes, node, skin_path_prefix=None, ignore=[]):
207         # walk all attributes
208         for a in node.items():
209                 #print a
210                 attrib = a[0]
211                 value = a[1]
212
213                 if attrib in ("pixmap", "pointer", "seek_pointer", "progress_pointer", "backgroundPixmap", "selectionPixmap", "scrollbarSliderPicture", "scrollbarSliderBackgroundPicture", "scrollbarValuePicture"):
214                         value = resolveFilename(SCOPE_SKIN_IMAGE, value, path_prefix=skin_path_prefix)
215
216                 if attrib not in ignore:
217                         skinAttributes.append((attrib, six.ensure_str(value)))
218
219 def loadPixmap(path, desktop, size=eSize()):
220         cached = False
221         option = path.find("#")
222         if option != -1:
223                 options = path[option+1:].split(',')
224                 path = path[:option]
225                 cached = "cached" in options
226         ptr = LoadPixmap(path, desktop, cached, size=size)
227         if ptr is None:
228                 raise SkinError("pixmap file %s not found!" % (path))
229         return ptr
230
231 def applySingleAttribute(guiObject, desktop, attrib, value, scale = ((1,1),(1,1))):
232         # and set attributes
233         if attrib == 'position':
234                 guiObject.move(parsePosition(value, scale, desktop, guiObject))
235         elif attrib == 'size':
236                 guiObject.resize(parseSize(value, scale, desktop, guiObject))
237         elif attrib == 'title':
238                 guiObject.setTitle(_(value))
239         elif attrib == 'text':
240                 guiObject.setText(_(value))
241         elif attrib == 'font':
242                 guiObject.setFont(parseFont(value, scale))
243         elif attrib == 'zPosition':
244                 guiObject.setZPosition(int(value))
245         elif attrib == 'itemHeight':
246                 guiObject.setItemHeight(int(value))
247         elif attrib == 'itemWidth':
248                 guiObject.setItemWidth(int(value))
249         elif attrib == 'mode':
250                 mode = {'vertical' : eListbox.layoutVertical,
251                                 'grid' : eListbox.layoutGrid,
252                                 'horizontal' : eListbox.layoutHorizontal
253                         }[value]
254                 guiObject.setMode(mode)
255         elif attrib == 'margin':
256                 leftRight, topBottom = [int(x) for x in value.split(",")]
257                 guiObject.setMargin(ePoint(leftRight, topBottom))
258         elif attrib == 'selectionZoom':
259                 guiObject.setSelectionZoom(float(value))
260         elif attrib in ("pixmap", "backgroundPixmap", "selectionPixmap", "scrollbarSliderPicture", "scrollbarSliderBackgroundPicture", "scrollbarValuePicture"):
261                 if attrib == "pixmap" and value.endswith("svg"):
262                         ptr = loadPixmap(value, desktop, guiObject.size())
263                 else:
264                         try:
265                                 ptr = loadPixmap(value, desktop) # this should already have been filename-resolved.
266                         except SkinError:
267                                 s = value.split('/')
268                                 if attrib == "pixmap" and len(s) > 2 and s[-2] == 'menu' and s[-1].endswith("png"):
269                                         Log.w("Please fix the skin... try .svg now");
270                                         value2 = value[:-3]
271                                         value2 += 'svg'
272                                         ptr = loadPixmap(value2, desktop, guiObject.size())
273                                 else:
274                                         raise
275                 if attrib == "pixmap":
276                         guiObject.setPixmap(ptr)
277                 elif attrib == "backgroundPixmap":
278                         guiObject.setBackgroundPicture(ptr)
279                 elif attrib == "selectionPixmap":
280                         guiObject.setSelectionPicture(ptr)
281                 elif attrib == "scrollbarSliderPicture":
282                         guiObject.setScrollbarSliderPicture(ptr)
283                 elif attrib == "scrollbarSliderBackgroundPicture":
284                         guiObject.setScrollbarSliderBackgroundPicture(ptr)
285                 elif attrib == "scrollbarValuePicture":
286                         guiObject.setScrollbarValuePicture(ptr)
287                 # guiObject.setPixmapFromFile(value)
288         elif attrib in ("alphatest", "blend"): # used by ePixmap
289                 guiObject.setAlphatest(
290                         { "on": 1,
291                           "off": 0,
292                           "blend": 2,
293                         }[value])
294         elif attrib == "scale":
295                 value = {
296                         "off" :  ePixmap.SCALE_TYPE_NONE,
297                         "none" :  ePixmap.SCALE_TYPE_NONE,
298                         "on" :  ePixmap.SCALE_TYPE_ASPECT,
299                         "aspect" : ePixmap.SCALE_TYPE_ASPECT,
300                         "center" : ePixmap.SCALE_TYPE_CENTER,
301                         "width" : ePixmap.SCALE_TYPE_WIDTH,
302                         "height" : ePixmap.SCALE_TYPE_HEIGHT,
303                         "stretch" : ePixmap.SCALE_TYPE_STRETCH,
304                         "fill" : ePixmap.SCALE_TYPE_FILL,
305                 }.get(value, ePixmap.SCALE_TYPE_ASPECT)
306                 guiObject.setScale(value)
307         elif attrib == "orientation": # used by eSlider
308                 try:
309                         guiObject.setOrientation(*
310                                 { "orVertical": (guiObject.orVertical, False),
311                                         "orTopToBottom": (guiObject.orVertical, False),
312                                         "orBottomToTop": (guiObject.orVertical, True),
313                                         "orHorizontal": (guiObject.orHorizontal, False),
314                                         "orLeftToRight": (guiObject.orHorizontal, False),
315                                         "orRightToLeft": (guiObject.orHorizontal, True),
316                                 }[value])
317                 except KeyError:
318                         print("oprientation must be either orVertical or orHorizontal!")
319         elif attrib == "valign":
320                 try:
321                         guiObject.setVAlign(
322                                 { "top": guiObject.alignTop,
323                                         "center": guiObject.alignCenter,
324                                         "bottom": guiObject.alignBottom,
325                                         "centerOrBottom" : guiObject.alignCenterOrBottom
326                                 }[value])
327                 except KeyError:
328                         print("valign must be either top, center, bottom or centerOrBottom!")
329         elif attrib == "halign":
330                 try:
331                         guiObject.setHAlign(
332                                 { "left": guiObject.alignLeft,
333                                         "center": guiObject.alignCenter,
334                                         "right": guiObject.alignRight,
335                                         "block": guiObject.alignBlock,
336                                         "centerOrRight": guiObject.alignCenterOrRight
337                                 }[value])
338                 except KeyError:
339                         print("halign must be either left, center, right, block or centerOrRight!")
340         elif attrib == "flags":
341                 flags = value.split(',')
342                 for f in flags:
343                         try:
344                                 fv = eWindow.__dict__[f]
345                                 guiObject.setFlag(fv)
346                         except KeyError:
347                                 print("illegal flag %s!" % f)
348         elif attrib == "padding":
349                 guiObject.setPadding(parsePosition(value, scale))
350         elif attrib in ("radius", "cornerRadius"):
351                 guiObject.setCornerRadius(int(value))
352         elif attrib == "gradient":
353                 values = value.split(',')
354                 direction = {
355                         "horizontal" : ePixmap.GRADIENT_HORIZONTAL,
356                         "vertical" : ePixmap.GRADIENT_VERTICAL,
357                         "horizontalCentered" : ePixmap.GRADIENT_HORIZONTAL_CENTERED,
358                         "verticalCentered" : ePixmap.GRADIENT_VERTICAL_CENTERED,
359                 }.get(values[2], ePixmap.GRADIENT_VERTICAL)
360                 guiObject.setGradient(parseColor(values[0]), parseColor(values[1]), direction)
361         elif attrib == "backgroundColor":
362                 guiObject.setBackgroundColor(parseColor(value))
363         elif attrib == "backgroundColorSelected":
364                 guiObject.setBackgroundColorSelected(parseColor(value))
365         elif attrib == "foregroundColor":
366                 guiObject.setForegroundColor(parseColor(value))
367         elif attrib == "foregroundColorSelected":
368                 guiObject.setForegroundColorSelected(parseColor(value))
369         elif attrib == "shadowColor":
370                 guiObject.setShadowColor(parseColor(value))
371         elif attrib == "selectionDisabled":
372                 guiObject.setSelectionEnable(0)
373         elif attrib == "transparent":
374                 guiObject.setTransparent(int(value))
375         elif attrib == "borderColor":
376                 guiObject.setBorderColor(parseColor(value))
377         elif attrib == "borderWidth":
378                 guiObject.setBorderWidth(int(value))
379         elif attrib == "scrollbarSliderBorderWidth":
380                 guiObject.setScrollbarSliderBorderWidth(int(value))
381         elif attrib == "scrollbarWidth":
382                 guiObject.setScrollbarWidth(int(value))
383         elif attrib == "scrollbarBackgroundPixmapTopHeight":
384                 guiObject.setScrollbarBackgroundPixmapTopHeight(int(value))
385         elif attrib == "scrollbarBackgroundPixmapBottomHeight":
386                 guiObject.setScrollbarBackgroundPixmapBottomHeight(int(value))
387         elif attrib == "scrollbarValuePixmapTopHeight":
388                 guiObject.setScrollbarValuePixmapTopHeight(int(value))
389         elif attrib == "scrollbarValuePixmapBottomHeight":
390                 guiObject.setScrollbarValuePixmapBottomHeight(int(value))
391         elif attrib == "scrollbarMode":
392                 guiObject.setScrollbarMode(
393                         { "showOnDemand": guiObject.showOnDemand,
394                                 "showAlways": guiObject.showAlways,
395                                 "showNever": guiObject.showNever
396                         }[value])
397         elif attrib == "enableWrapAround":
398                 guiObject.setWrapAround(True)
399         elif attrib == "backlogMode":
400                 guiObject.setBacklogMode(True)
401         elif attrib == "pointer" or attrib == "seek_pointer" or attrib == "progress_pointer":
402                 (name, pos) = value.split(':')
403                 pos = parsePosition(pos, scale)
404                 ptr = loadPixmap(name, desktop)
405                 guiObject.setPointer({"pointer": 0, "seek_pointer": 1, "progress_pointer": 2}[attrib], ptr, pos)
406         elif attrib == 'shadowOffset':
407                 guiObject.setShadowOffset(parsePosition(value, scale))
408         elif attrib == 'noWrap':
409                 guiObject.setNoWrap(1)
410         elif attrib == 'id':
411                 pass
412         else:
413                 print("WARNING!!!!: unsupported skin attribute " + attrib + "=" + value)
414
415 def applyAllAttributes(guiObject, desktop, attributes, scale, skipZPosition=False):
416         size_key = 'size'
417         pos_key = 'position'
418         zpos_key = 'zPosition'
419         pixmap_key = 'pixmap'
420         background_key = 'backgroundColor'
421         roundedlabel_key = 'roundedlabelColor'
422
423         size_val = pos_val = pixmap_val = background_val = None
424         for (attrib, value) in attributes:
425                 if attrib == pos_key:
426                         pos_val = value
427                 elif attrib == size_key:
428                         size_val = value
429                 #SVG's really should be scaled at load-time and not by the GPU, so we handle that exception
430                 elif attrib == pixmap_key and value.endswith("svg"):
431                         pixmap_val = value
432                 elif attrib == background_key:
433                         if not background_val: #prioritize roundedlabelColor so merlin stuff keeps working
434                                 background_val = value
435                 elif attrib == roundedlabel_key:
436                         background_val = value
437                 elif skipZPosition and attrib == zpos_key:
438                         pass
439                 else:
440                         applySingleAttribute(guiObject, desktop, attrib, value, scale)
441
442         if background_val is not None:
443                 applySingleAttribute(guiObject, desktop, background_key, background_val, scale)
444         #relative positioning only works if we have the sized the widget before positioning
445         if size_val is not None:
446                 applySingleAttribute(guiObject, desktop, size_key, size_val, scale)
447         if pos_val is not None:
448                 applySingleAttribute(guiObject, desktop, pos_key, pos_val, scale)
449         if pixmap_val is not None:
450                 applySingleAttribute(guiObject, desktop, pixmap_key, pixmap_val, scale)
451
452 def loadSingleSkinData(desktop, skin, path_prefix):
453         """loads skin data like colors, windowstyle etc."""
454         assert skin.tag == "skin", "root element in skin must be 'skin'!"
455
456         #print "***SKIN: ", path_prefix
457
458         for c in skin.findall("output"):
459                 id = c.attrib.get('id')
460                 if id:
461                         id = int(id)
462                 else:
463                         id = 0
464                 if id == 0: # framebuffer
465                         for res in c.findall("resolution"):
466                                 xres = int(res.get("xres", "720"))
467                                 yres = int(res.get("yres", "576"))
468                                 bpp = int(res.get("bpp", "32"))
469                                 from enigma import gMainDC
470                                 gMainDC.getInstance().setResolution(xres, yres, bpp)
471                                 desktop.resize(eSize(xres, yres))
472                                 break
473
474         for c in skin.findall("colors"):
475                 for color in c.findall("color"):
476                         get_attr = color.attrib.get
477                         name = get_attr("name")
478                         color = get_attr("value")
479                         if name and color:
480                                 colorNames[name] = parseColor(color)
481                                 #print "Color:", name, color
482                         else:
483                                 raise SkinError("need color and name, got %s %s" % (name, color))
484
485         for c in skin.findall("listboxcontent"):
486                 for offset in c.findall("offset"):
487                         get_attr = offset.attrib.get
488                         name = get_attr("name")
489                         value = get_attr("value")
490                         if name and value:
491                                 if name == "left":
492                                         eListboxPythonStringContent.setLeftOffset(parseValue(value))
493                                 elif name == "right":
494                                         eListboxPythonStringContent.setRightOffset(parseValue(value))
495                                 else:
496                                         raise SkinError("got listboxcontent offset '%s'' but 'left' or 'right' is allowed only" % name)
497                 for font in c.findall("font"):
498                         get_attr = font.attrib.get
499                         name = get_attr("name")
500                         font = get_attr("font")
501                         if name and font:
502                                 if name == "string":
503                                         eListboxPythonStringContent.setFont(parseFont(font, ((1,1),(1,1))))
504                                 elif name == "config_description":
505                                         eListboxPythonConfigContent.setDescriptionFont(parseFont(font, ((1,1),(1,1))))
506                                 elif name == "config_value":
507                                         eListboxPythonConfigContent.setValueFont(parseFont(font, ((1,1),(1,1))))
508                                 else:
509                                         raise SkinError("got listboxcontent font '%s' but 'string', 'config_description' or 'config_value' is allowed only" % name)
510                 for value in c.findall("value"):
511                         get_attr = value.attrib.get
512                         name = get_attr("name")
513                         value = get_attr("value")
514                         if name and value:
515                                 if name == "string_item_height":
516                                         eListboxPythonStringContent.setItemHeight(parseValue(value))
517                                 elif name == "config_item_height":
518                                         eListboxPythonConfigContent.setItemHeight(parseValue(value))
519                                 else:
520                                         raise SkinError("got listboxcontent value '%s' but 'string_item_height' or 'config_item_height' is allowed only" % name)
521                 for cfgpm in c.findall("config"):
522                         onPath =  cfgpm.attrib.get("onPixmap")
523                         if not fileExists(onPath):
524                                 onPath = resolveFilename(SCOPE_CURRENT_SKIN, onPath)
525                         offPath =  cfgpm.attrib.get("offPixmap")
526                         if not fileExists(offPath):
527                                 offPath = resolveFilename(SCOPE_CURRENT_SKIN, offPath)
528                         pixmapSize = cfgpm.attrib.get("size")
529                         if pixmapSize:
530                                 pixmapSize = parseSize(pixmapSize, ((1,1),(1,1)))
531                         else:
532                                 pixmapSize = eSize()
533                         ConfigBoolean.setOnOffPixmaps(loadPixmap(onPath, desktop, pixmapSize), loadPixmap(offPath, desktop, pixmapSize))
534         for c in skin.findall("fonts"):
535                 for font in c.findall("font"):
536                         get_attr = font.attrib.get
537                         filename = get_attr("filename", "<NONAME>")
538                         name = get_attr("name", "Regular")
539                         scale = get_attr("scale")
540                         if scale:
541                                 scale = int(scale)
542                         else:
543                                 scale = 100
544                         is_replacement = get_attr("replacement") and True or False
545                         resolved_font = resolveFilename(SCOPE_FONTS, filename, path_prefix=path_prefix)
546                         if not fileExists(resolved_font): #when font is not available look at current skin path
547                                 skin_path = resolveFilename(SCOPE_CURRENT_SKIN, filename)
548                                 if fileExists(skin_path):
549                                         resolved_font = skin_path
550                         addFont(resolved_font, name, scale, is_replacement)
551                         #print "Font: ", resolved_font, name, scale, is_replacement
552
553         for c in skin.findall("subtitles"):
554                 from enigma import eSubtitleWidget
555                 scale = ((1,1),(1,1))
556                 for substyle in c.findall("sub"):
557                         get_attr = substyle.attrib.get
558                         font = parseFont(get_attr("font"), scale)
559                         col = get_attr("foregroundColor")
560                         if col:
561                                 foregroundColor = parseColor(col)
562                                 haveColor = 1
563                         else:
564                                 foregroundColor = gRGB(0xFFFFFF)
565                                 haveColor = 0
566                         col = get_attr("shadowColor")
567                         if col:
568                                 shadowColor = parseColor(col)
569                         else:
570                                 shadowColor = gRGB(0)
571                         shadowOffset = parsePosition(get_attr("shadowOffset"), scale)
572                         face = eSubtitleWidget.__dict__[get_attr("name")]
573                         eSubtitleWidget.setFontStyle(face, font, haveColor, foregroundColor, shadowColor, shadowOffset)
574
575         for windowstyle in skin.findall("windowstyle"):
576                 style = eWindowStyleSkinned()
577                 id = windowstyle.attrib.get("id")
578                 if id:
579                         id = int(id)
580                 else:
581                         id = 0
582                 #print "windowstyle:", id
583
584                 # defaults
585                 font = gFont("Regular", 20)
586                 offset = eSize(20, 5)
587
588                 for title in windowstyle.findall("title"):
589                         get_attr = title.attrib.get
590                         offset = parseSize(get_attr("offset"), ((1,1),(1,1)))
591                         font = parseFont(get_attr("font"), ((1,1),(1,1)))
592
593                 style.setTitleFont(font);
594                 style.setTitleOffset(offset)
595                 #print "  ", font, offset
596
597                 for borderset in windowstyle.findall("borderset"):
598                         bsName = str(borderset.attrib.get("name"))
599                         for pixmap in borderset.findall("pixmap"):
600                                 get_attr = pixmap.attrib.get
601                                 bpName = get_attr("pos")
602                                 if "filename" in pixmap.attrib:
603                                         filename = get_attr("filename")
604                                         if filename and bpName:
605                                                 png = loadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix), desktop)
606                                                 style.setPixmap(eWindowStyleSkinned.__dict__[bsName], eWindowStyleSkinned.__dict__[bpName], png)
607                                 elif "color" in pixmap.attrib:
608                                         color = parseColor(get_attr("color"))
609                                         size = int(get_attr("size"))
610                                         Log.w("%s: %s @ %s" %(bpName, color.argb(), size))
611                                         style.setColorBorder(eWindowStyleSkinned.__dict__[bsName], eWindowStyleSkinned.__dict__[bpName], color, size)
612
613                 for color in windowstyle.findall("color"):
614                         get_attr = color.attrib.get
615                         colorType = get_attr("name")
616                         color = parseColor(get_attr("color"))
617                         try:
618                                 style.setColor(eWindowStyleSkinned.__dict__["col" + colorType], color)
619                         except:
620                                 raise SkinError("Unknown color %s" % (colorType))
621
622                 for listfont in windowstyle.findall("listfont"):
623                         get_attr = listfont.attrib.get
624                         fontType = get_attr("type")
625                         fontSize = int(get_attr("size"))
626                         fontFace = get_attr("font")
627                         try:
628                                 Log.i("########### ADDING %s: %s" %(fontType, fontSize))
629                                 style.setListFont(eWindowStyleSkinned.__dict__["listFont" + fontType], fontSize, fontFace)
630                         except:
631                                 raise SkinError("Unknown listFont %s" % (fontType))
632
633                 x = eWindowStyleManager.getInstance()
634                 x.setStyle(id, style)
635
636         for windowstylescrollbar in skin.findall("windowstylescrollbar"):
637                 style = eWindowStyleScrollbar()
638                 id = windowstylescrollbar.attrib.get("id")
639                 if id:
640                         id = int(id)
641                 else:
642                         id = 4
643                 for value in windowstylescrollbar.findall("value"):
644                         get_attr = value.attrib.get
645                         vType = get_attr("name")
646                         v = get_attr("value")
647                         if vType in ("BackgroundPixmapTopHeight", "BackgroundPixmapBeginSize"):
648                                 style.setBackgroundPixmapTopHeight(int(v))
649                         elif vType in ("BackgroundPixmapBottomHeight", "BackgroundPixmapEndSize"):
650                                 style.setBackgroundPixmapBottomHeight(int(v))
651                         elif vType in ("ValuePixmapTopHeight", "ValuePixmapBeginSize"):
652                                 style.setValuePixmapTopHeight(int(v))
653                         elif vType in ("ValuePixmapBottomHeight", "ValuePixmapEndSize"):
654                                 style.setValuePixmapBottomHeight(int(v))
655                         elif vType == "ScrollbarWidth":
656                                 style.setScrollbarWidth(int(v))
657                         elif vType == "ScrollbarBorderWidth":
658                                 style.setScrollbarBorderWidth(int(v))
659                 for pixmap in windowstylescrollbar.findall("pixmap"):
660                         get_attr = pixmap.attrib.get
661                         vType = get_attr("name")
662                         filename = get_attr("filename")
663                         if filename:
664                                 if vType == "BackgroundPixmap":
665                                         png = loadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix), desktop)
666                                         style.setBackgroundPixmap(png)
667                                 elif vType == "ValuePixmap":
668                                         png = loadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix), desktop)
669                                         style.setValuePixmap(png)
670                 x = eWindowStyleManager.getInstance()
671                 x.setStyle(id, style)
672
673         for g in skin.findall("globals"):
674                 for value in g.findall("value"):
675                         Log.i("Global skin value : %s" %(value.attrib,))
676                         skinGlobals[value.attrib["name"]] = value.attrib["value"]
677
678         for components in skin.findall("components"):
679                 for component in components.findall("component"):
680                         componentSizes.apply(component.attrib)
681                         for template in component.findall("template"):
682                                 componentSizes.addTemplate(component.attrib, template.text)
683
684         for l in skin.findall("layouts"):
685                 for layout in l.findall("layout"):
686                         layouts.apply(layout)
687
688 def loadSkinData(desktop):
689         skins = dom_skins[:]
690         skins.reverse()
691         for (path, dom_skin) in skins:
692                 loadSingleSkinData(desktop, dom_skin, path)
693
694 def lookupScreen(name, style_id):
695         for (path, skin) in dom_skins:
696                 # first, find the corresponding screen element
697                 for x in skin.findall("screen"):
698                         if x.attrib.get('name', '') == name:
699                                 screen_style_id = x.attrib.get('id', '-1')
700                                 if screen_style_id == '-1' and name.find('ummary') > 0:
701                                         screen_style_id = '1'
702                                 if (style_id != 2 and int(screen_style_id) == -1) or int(screen_style_id) == style_id:
703                                         return x, path
704         return None, None
705
706 class WidgetGroup():
707         def __init__(self, screen):
708                 self.children = []
709                 self._screen = screen
710                 self.visible = 1
711
712         def append(self, child):
713                 self.children.append(child)
714
715         def hide(self):
716                 self.visible = 0
717                 for child in self.children:
718                         if isinstance(child, additionalWidget):
719                                 child.instance.hide()
720                         elif isinstance(child, six.string_types):
721                                 self._screen[child].hide()
722                         else:
723                                 child.hide()
724
725         def show(self):
726                 self.visible = 1
727                 for child in self.children:
728                         if isinstance(child, additionalWidget):
729                                 child.instance.show()
730                         elif isinstance(child, six.string_types):
731                                 self._screen[child].show()
732                         else:
733                                 child.show()
734
735         def execBegin(self):
736                 pass
737
738         def execEnd(self):
739                 pass
740
741         def destroy(self):
742                 pass
743
744 class additionalWidget:
745         pass
746
747 class Layouts():
748         def __init__(self):
749                 self.layouts = {}
750
751         def __getitem__(self, key):
752                 return key in self.layouts and self.layouts[key] or {}
753
754         def apply(self, node):
755                 get_attr = node.attrib.get
756                 key = get_attr("name")
757                 filename = get_attr("filename")
758                 if filename:
759                         xml_filename = resolveFilename(SCOPE_SKIN_IMAGE, filename)
760                         node = xml.etree.cElementTree.parse(xml_filename).getroot()
761                 self.layouts[key] = node.getchildren()
762
763 layouts = Layouts()
764
765 class ComponentSizes():
766         CONFIG_LIST = "ConfigList"
767         CHOICELIST = "ChoiceList"
768         FILE_LIST = "FileList"
769         MULTI_FILE_SELECT_LIST = "MultiFileSelectList"
770         HELP_MENU_LIST = "HelpMenuList"
771         PARENTAL_CONTROL_LIST = "ParentalControlList"
772         SELECTION_LIST = "SelectionList"
773         SERVICE_LIST = "ServiceList"
774         SERVICE_INFO_LIST = "ServiceInfoList"
775         TIMER_LIST = "TimerList"
776         MOVIE_LIST = "MovieList"
777         NIM_SETUP = "NimSetup"
778         TIMELINE_TEXT = "TimelineText"
779         MENU_PIXMAP = "MenuPixmap"
780         ITEM_HEIGHT = "itemHeight"
781         ITEM_WIDTH = "itemWidth"
782         TEMPLATE = "template"
783         TEXT_X = "textX"
784         TEXT_Y = "textY"
785         TEXT_WIDTH = "textWidth"
786         TEXT_HEIGHT = "textHeight"
787         PIXMAP_X = "pixmapX"
788         PIXMAP_Y = "pixmapY"
789         PIXMAP_WIDTH = "pixmapWidth"
790         PIXMAP_HEIGHT = "pixmapHeight"
791
792         def __init__(self, style_id = 0):
793                 self.components = {}
794
795         def apply(self, attribs):
796                 values = {}
797                 key = None
798                 for a in attribs.items():
799                         if a[0] == "type":
800                                 key = a[1]
801                         else:
802                                 values[a[0]] = int(a[1])
803                 if key:
804                         self.components[key] = values
805
806         def addTemplate(self, attribs, template):
807                 key = attribs.get("type", None)
808                 if key:
809                         self.components[key][self.TEMPLATE] = template.strip()
810
811         def __getitem__(self, component_id):
812                 return component_id in self.components and self.components[component_id] or {}
813
814         def itemHeight(self, component_id, default=None):
815                 val = component_id in self.components and self.components[component_id].get(self.ITEM_HEIGHT, default) or default
816                 if not val:
817                         val = 30 #30 is quite random, I went for a small value because that would probably fit most of the time
818                         Log.w("No itemWidth set for %s and default is %s falling back to %s") %(component_id, str(default), val)
819                 return val
820
821         def itemWidth(self, component_id, default=None):
822                 val = component_id in self.components and self.components[component_id].get(self.ITEM_WIDTH, default) or default
823                 if not val:
824                         val = 30 #30 is quite random, I went for a small value because that would probably fit most of the time
825                         Log.w("No itemWidth set for %s and default is %s falling back to %s") %(component_id, str(default), val)
826                 return val
827
828         def template(self, component_id):
829                 return component_id in self.components and self.components[component_id].get(self.TEMPLATE, None) or None
830
831 componentSizes = ComponentSizes()
832
833 def readSkin(screen, skin, names, desktop):
834         if not isinstance(names, list):
835                 names = [names]
836
837         name = "<embedded-in-'%s'>" % screen.__class__.__name__
838
839         style_id = desktop.getStyleID();
840
841         # try all skins, first existing one have priority
842         for n in names:
843                 myscreen, path = lookupScreen(n, style_id)
844                 if myscreen is not None:
845                         # use this name for debug output
846                         name = n
847                         break
848
849         # otherwise try embedded skin
850         if myscreen is None:
851                 myscreen = getattr(screen, "parsedSkin", None)
852
853         # try uncompiled embedded skin
854         if myscreen is None and getattr(screen, "skin", None):
855                 print("Looking for embedded skin")
856                 skin_tuple = screen.skin
857                 if not isinstance(skin_tuple, tuple):
858                         skin_tuple = (skin_tuple,)
859                 for sskin in skin_tuple:
860                         parsedSkin = xml.etree.cElementTree.fromstring(sskin)
861                         screen_style_id = parsedSkin.attrib.get('id', '-1')
862                         if (style_id != 2 and int(screen_style_id) == -1) or int(screen_style_id) == style_id:
863                                 myscreen = screen.parsedSkin = parsedSkin
864                                 break
865
866         #assert myscreen is not None, "no skin for screen '" + repr(names) + "' found!"
867         if myscreen is None:
868                 print("No skin to read...")
869                 emptySkin = "<screen></screen>"
870                 myscreen = screen.parsedSkin = xml.etree.cElementTree.fromstring(emptySkin)
871
872         screen.skinAttributes = [ ]
873
874         skin_path_prefix = getattr(screen, "skin_path", path)
875
876         for widget in myscreen.getchildren():
877                 if widget.tag == "layout":
878                         get_attr = widget.attrib.get
879                         for layout in layouts[get_attr('name')]:
880                                 myscreen.append(layout)
881                         myscreen.remove(widget)
882
883         collectAttributes(screen.skinAttributes, myscreen, skin_path_prefix, ignore=["name"])
884
885         screen.additionalWidgets = [ ]
886         screen.renderer = [ ]
887
888         visited_components = set()
889
890         # now walk all widgets
891         parseWidgets(name, myscreen, screen, skin_path_prefix, visited_components)
892
893         from Components.GUIComponent import GUIComponent
894         nonvisited_components = [x for x in set(screen.keys()) - visited_components if isinstance(x, GUIComponent)]
895         assert not nonvisited_components, "the following components in %s don't have a skin entry: %s" % (name, ', '.join(nonvisited_components))
896
897 def parseWidgets(name, node, screen, skin_path_prefix, visited_components, group=None):
898         for widget in node.getchildren():
899                 w_tag = widget.tag
900
901                 if w_tag == "group":
902                         gname = widget.attrib.get('name')
903                         assert(gname) not in list(screen.keys()), "element with name %s already exists in %s!" % (gname, screen.skinName)
904                         inner_group = WidgetGroup(screen)
905                         screen[gname] = inner_group
906                         if group is not None:
907                                 group.append(inner_group)
908                         visited_components.add(gname)
909                         parseWidgets(name, widget, screen, skin_path_prefix, visited_components, inner_group)
910                         continue
911
912                 if w_tag == "widget":
913                         parseWidget(name, widget, screen, skin_path_prefix, visited_components, group)
914                         continue
915
916                 if w_tag == "applet":
917                         try:
918                                 codeText = widget.text.strip()
919                         except:
920                                 codeText = ""
921
922                         #print "Found code:"
923                         #print codeText
924                         widgetType = widget.attrib.get('type')
925
926                         if "//" in codeText:
927                                 code = compile(codeText, "skin applet", "exec")
928                         else:
929                                 code = skin_applet_compile(codeText, "skin applet", "exec")
930
931                         if widgetType == "onLayoutFinish":
932                                 screen.onLayoutFinish.append(code)
933                                 #print "onLayoutFinish = ", codeText
934                         else:
935                                 raise SkinError("applet type '%s' unknown!" % widgetType)
936                                 #print "applet type '%s' unknown!" % type
937
938                         continue
939
940                 w = additionalWidget()
941
942                 if w_tag == "eLabel":
943                         w.widget = eLabel
944                 elif w_tag == "ePixmap":
945                         w.widget = ePixmap
946                 else:
947                         raise SkinError("unsupported stuff : %s" % w_tag)
948                         #print "unsupported stuff : %s" % widget.tag
949
950                 w.skinAttributes = [ ]
951                 collectAttributes(w.skinAttributes, widget, skin_path_prefix, ignore=['name'])
952
953                 if group is not None:
954                         group.append(w)
955
956                 # applyAttributes(guiObject, widget, desktop)
957                 # guiObject.thisown = 0
958                 screen.additionalWidgets.append(w)
959
960 def parseWidget(name, widget, screen, skin_path_prefix, visited_components, group):
961         get_attr = widget.attrib.get
962         # ok, we either have 1:1-mapped widgets ('old style'), or 1:n-mapped
963         # widgets (source->renderer).
964         wname = get_attr('name')
965         wsource = get_attr('source')
966
967         if wname is None and wsource is None:
968                 print("widget has no name and no source!")
969                 return
970
971         if wname:
972                 # print "Widget name=", wname
973                 visited_components.add(wname)
974
975                 # get corresponding 'gui' object
976                 try:
977                         attributes = screen[wname].skinAttributes = [ ]
978                         if group is not None:
979                                 group.append(wname)
980                 except:
981                         raise SkinError("component with name '" + wname + "' was not found in skin of screen '" + name + "'!")
982                         # print "WARNING: component with name '" + wname + "' was not found in skin of screen '" + name + "'!"
983
984 #                       assert screen[wname] is not Source
985
986                 # and collect attributes for this
987                 collectAttributes(attributes, widget, skin_path_prefix, ignore=['name'])
988         elif wsource and not screen.ignoreSource(wsource):
989                 # get corresponding source
990                 # print "Widget source=", wsource
991
992                 while True:  # until we found a non-obsolete source
993
994                         # parse our current "wsource", which might specifiy a "related screen" before the dot,
995                         # for example to reference a parent, global or session-global screen.
996                         scr = screen
997
998                         # resolve all path components
999                         path = wsource.split('.')
1000                         while len(path) > 1:
1001                                 scr = screen.getRelatedScreen(path[0])
1002                                 if scr is None:
1003                                         # print wsource
1004                                         # print name
1005                                         raise SkinError("specified related screen '" + wsource + "' was not found in screen '" + name + "'!")
1006                                 path = path[1:]
1007
1008                         # resolve the source.
1009                         source = scr.get(path[0])
1010                         if isinstance(source, ObsoleteSource):
1011                                 # however, if we found an "obsolete source", issue warning, and resolve the real source.
1012                                 print("WARNING: SKIN '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source))
1013                                 print("OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date))
1014                                 if source.description:
1015                                         print(source.description)
1016
1017                                 wsource = source.new_source
1018                         else:
1019                                 # otherwise, use that source.
1020                                 break
1021
1022                 if source is None:
1023                         raise SkinError("source '" + wsource + "' was not found in screen '" + name + "'!")
1024
1025                 tmp = get_attr('render').split(',')
1026                 wrender = tmp[0]
1027                 if len(tmp) > 1:
1028                         wrender_args = tmp[1:]
1029                 else:
1030                         wrender_args = tuple()
1031
1032                 if not wrender:
1033                         raise SkinError("you must define a renderer with render= for source '%s'" % (wsource))
1034
1035                 for converter in widget.findall("convert"):
1036                         ctype = converter.get('type')
1037                         assert ctype, "'convert'-tag needs a 'type'-attribute"
1038                         # print "Converter:", ctype
1039                         try:
1040                                 parms = converter.text.strip()
1041                         except:
1042                                 parms = ""
1043                         # print "Params:", parms
1044                         converter_class = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
1045
1046                         c = None
1047
1048                         for i in source.downstream_elements:
1049                                 if isinstance(i, converter_class) and i.converter_arguments == parms:
1050                                         c = i
1051
1052                         if c is None:
1053                                 print("allocating new converter!")
1054                                 c = converter_class(parms)
1055                                 c.connect(source)
1056                         else:
1057                                 print("reused converter!")
1058                                 hasattr(c, "reuse") and c.reuse()
1059
1060                         source = c
1061
1062                 renderer_class = my_import('.'.join(("Components", "Renderer", wrender))).__dict__.get(wrender)
1063
1064                 renderer = renderer_class(*wrender_args)  # instantiate renderer
1065
1066                 renderer.connect(source)  # connect to source
1067                 attributes = renderer.skinAttributes = [ ]
1068                 collectAttributes(attributes, widget, skin_path_prefix, ignore=['render', 'source'])
1069
1070                 screen.renderer.append(renderer)
1071                 if group is not None:
1072                         group.append(renderer)
1073
1074 class TemplatedColors():
1075         def __init__(self, style_id = 0):
1076                 x = eWindowStyleManager.getInstance()
1077                 style = x.getStyle(style_id)
1078                 self.colors = {}
1079                 for color_name in ("Background", "LabelForeground", "ListboxForeground", "ListboxSelectedForeground", "ListboxBackground", "ListboxSelectedBackground", "ListboxMarkedForeground", "ListboxMarkedAndSelectedForeground", "ListboxMarkedBackground", "ListboxMarkedAndSelectedBackground", "WindowTitleForeground", "WindowTitleBackground"):
1080                         color = gRGB(0)
1081                         style.getColor(eWindowStyleSkinned.__dict__["col"+color_name], color)
1082                         self.colors[color_name] = color
1083
1084         def __getitem__(self, color_name):
1085                 return color_name in self.colors and self.colors[color_name] or gRGB(0)
1086
1087 class TemplatedListFonts():
1088         KEYBOARD = "Keyboard"
1089         BIGGER = "Bigger"
1090         BIG = "Big"
1091         MEDIUM = "Medium"
1092         SMALL = "Small"
1093         SMALLER = "Smaller"
1094
1095         def __init__(self, style_id = 0):
1096                 x = eWindowStyleManager.getInstance()
1097                 style = x.getStyle(style_id)
1098                 self.sizes = {}
1099                 self.faces = {}
1100                 for font_id in (self.KEYBOARD, self.BIGGER, self.BIG, self.MEDIUM, self.SMALL, self.SMALLER):
1101                         size = int(style.getListFontSize(eWindowStyleSkinned.__dict__["listFont" + font_id]))
1102                         face = style.getListFontFace(eWindowStyleSkinned.__dict__["listFont" + font_id])
1103                         Log.i("%s: %s, %s" %(font_id, size, face))
1104                         self.sizes[font_id] = size
1105                         self.faces[font_id] = face
1106
1107         def size(self, font_id, default=20):
1108                 return font_id in self.sizes and self.sizes[font_id] or default
1109
1110         def face(self, font_id, default="Regular"):
1111                 return font_id in self.faces and self.faces[font_id] or default