add support for new skins, add per-skin image paths
[openblackhole/openblackhole-enigma2.git] / skin.py
1 from enigma import *
2 import xml.dom.minidom
3 from xml.dom import EMPTY_NAMESPACE
4 from Tools.Import import my_import
5 import os
6
7 from Tools.XMLTools import elementsWithTag, mergeText
8
9 colorNames = dict()
10
11 def dump(x, i=0):
12         print " " * i + str(x)
13         try:
14                 for n in x.childNodes:
15                         dump(n, i + 1)
16         except:
17                 None
18
19 from Tools.Directories import resolveFilename, SCOPE_SKIN, SCOPE_SKIN_IMAGE, SCOPE_FONTS
20
21 dom_skins = [ ]
22
23 def loadSkin(name):
24         # read the skin
25         filename = resolveFilename(SCOPE_SKIN, name)
26         path = os.path.dirname(filename) + "/"
27         dom_skins.append((path, xml.dom.minidom.parse(filename)))
28
29 # we do our best to always select the "right" value
30 # skins are loaded in order of priority: skin with
31 # highest priority is loaded last, usually the user-provided
32 # skin.
33
34 # currently, loadSingleSkinData (colors, bordersets etc.)
35 # are applied one-after-each, in order of ascending priority.
36 # the dom_skin will keep all screens in descending priority,
37 # so the first screen found will be used.
38
39 # example: loadSkin("nemesis_greenline/skin.xml")
40 loadSkin('skin.xml')
41 loadSkin('skin_default.xml')
42
43 def parsePosition(str):
44         x, y = str.split(',')
45         return ePoint(int(x), int(y))
46
47 def parseSize(str):
48         x, y = str.split(',')
49         return eSize(int(x), int(y))
50
51 def parseFont(str):
52         name, size = str.split(';')
53         return gFont(name, int(size))
54
55 def parseColor(str):
56         if str[0] != '#':
57                 try:
58                         return colorNames[str]
59                 except:
60                         raise ("color '%s' must be #aarrggbb or valid named color" % (str))
61         return gRGB(int(str[1:], 0x10))
62
63 def collectAttributes(skinAttributes, node, skin_path_prefix=None, ignore=[]):
64         # walk all attributes
65         for p in range(node.attributes.length):
66                 a = node.attributes.item(p)
67                 
68                 # convert to string (was: unicode)
69                 attrib = str(a.name)
70                 # TODO: localization? as in e1?
71                 value = a.value.encode("utf-8")
72                 
73                 if attrib in ["pixmap", "pointer"]:
74                         value = resolveFilename(SCOPE_SKIN_IMAGE, value, path_prefix=skin_path_prefix)
75                 
76                 if attrib not in ignore:
77                         skinAttributes.append((attrib, value))
78
79 def loadPixmap(path):
80         ptr = loadPNG(path)
81         if ptr is None:
82                 raise "pixmap file %s not found!" % (path)
83         return ptr
84
85 def applySingleAttribute(guiObject, desktop, attrib, value):
86         # and set attributes
87         try:
88                 if attrib == 'position':
89                         guiObject.move(parsePosition(value))
90                 elif attrib == 'size':
91                         guiObject.resize(parseSize(value))
92                 elif attrib == 'title':
93                         guiObject.setTitle(_(value))
94                 elif attrib == 'text':
95                         guiObject.setText(value)
96                 elif attrib == 'font':
97                         guiObject.setFont(parseFont(value))
98                 elif attrib == 'zPosition':
99                         guiObject.setZPosition(int(value))
100                 elif attrib == "pixmap":
101                         ptr = loadPixmap(value) # this should already have been filename-resolved.
102                         # that __deref__ still scares me!
103                         desktop.makeCompatiblePixmap(ptr.__deref__())
104                         guiObject.setPixmap(ptr.__deref__())
105                         # guiObject.setPixmapFromFile(value)
106                 elif attrib == "alphatest": # used by ePixmap
107                         guiObject.setAlphatest(
108                                 { "on": True,
109                                   "off": False
110                                 }[value])
111                 elif attrib == "orientation": # used by eSlider
112                         try:
113                                 guiObject.setOrientation(
114                                         { "orVertical": guiObject.orVertical,
115                                                 "orHorizontal": guiObject.orHorizontal
116                                         }[value])
117                         except KeyError:
118                                 print "oprientation must be either orVertical or orHorizontal!"
119                 elif attrib == "valign":
120                         try:
121                                 guiObject.setVAlign(
122                                         { "top": guiObject.alignTop,
123                                                 "center": guiObject.alignCenter,
124                                                 "bottom": guiObject.alignBottom
125                                         }[value])
126                         except KeyError:
127                                 print "valign must be either top, center or bottom!"
128                 elif attrib == "halign":
129                         try:
130                                 guiObject.setHAlign(
131                                         { "left": guiObject.alignLeft,
132                                                 "center": guiObject.alignCenter,
133                                                 "right": guiObject.alignRight,
134                                                 "block": guiObject.alignBlock
135                                         }[value])
136                         except KeyError:
137                                 print "halign must be either left, center, right or block!"
138                 elif attrib == "flags":
139                         flags = value.split(',')
140                         for f in flags:
141                                 try:
142                                         fv = eWindow.__dict__[f]
143                                         guiObject.setFlag(fv)
144                                 except KeyError:
145                                         print "illegal flag %s!" % f
146                 elif attrib == "backgroundColor":
147                         guiObject.setBackgroundColor(parseColor(value))
148                 elif attrib == "foregroundColor":
149                         guiObject.setForegroundColor(parseColor(value))
150                 elif attrib == "shadowColor":
151                         guiObject.setShadowColor(parseColor(value))
152                 elif attrib == "selectionDisabled":
153                         guiObject.setSelectionEnable(0)
154                 elif attrib == "transparent":
155                         guiObject.setTransparent(int(value))
156                 elif attrib == "borderColor":
157                         guiObject.setBorderColor(parseColor(value))
158                 elif attrib == "borderWidth":
159                         guiObject.setBorderWidth(int(value))
160                 elif attrib == "scrollbarMode":
161                         guiObject.setScrollbarMode(
162                                 { "showOnDemand": guiObject.showOnDemand,
163                                         "showAlways": guiObject.showAlways,
164                                         "showNever": guiObject.showNever
165                                 }[value])
166                 elif attrib == "enableWrapAround":
167                         guiObject.setWrapAround(True)
168                 elif attrib == "pointer":
169                         (name, pos) = value.split(':')
170                         pos = parsePosition(pos)
171                         ptr = loadPixmap(name)
172                         desktop.makeCompatiblePixmap(ptr.__deref__())
173                         guiObject.setPointer(ptr.__deref__(), pos)
174                 elif attrib == 'shadowOffset':
175                         guiObject.setShadowOffset(parsePosition(value))
176                 else:
177                         print "unsupported attribute " + attrib + "=" + value
178         except int:
179 # AttributeError:
180                 print "widget %s (%s) doesn't support attribute %s!" % ("", guiObject.__class__.__name__, attrib)
181
182 def applyAllAttributes(guiObject, desktop, attributes):
183         for (attrib, value) in attributes:
184                 applySingleAttribute(guiObject, desktop, attrib, value)
185
186 def loadSingleSkinData(desktop, dom_skin, path_prefix):
187         """loads skin data like colors, windowstyle etc."""
188         
189         skin = dom_skin.childNodes[0]
190         assert skin.tagName == "skin", "root element in skin must be 'skin'!"
191         
192         for c in elementsWithTag(skin.childNodes, "colors"):
193                 for color in elementsWithTag(c.childNodes, "color"):
194                         name = str(color.getAttribute("name"))
195                         color = str(color.getAttribute("value"))
196                         
197                         if not len(color):
198                                 raise ("need color and name, got %s %s" % (name, color))
199                                 
200                         colorNames[name] = parseColor(color)
201         
202         for c in elementsWithTag(skin.childNodes, "fonts"):
203                 for font in elementsWithTag(c.childNodes, "font"):
204                         filename = str(font.getAttribute("filename") or "<NONAME>")
205                         name = str(font.getAttribute("name") or "Regular")
206                         scale = int(font.getAttribute("scale") or "100")
207                         is_replacement = font.getAttribute("replacement") != ""
208                         addFont(resolveFilename(SCOPE_FONTS, filename, path_prefix=path_prefix), name, scale, is_replacement)
209         
210         for windowstyle in elementsWithTag(skin.childNodes, "windowstyle"):
211                 style = eWindowStyleSkinned()
212                 
213                 # defaults
214                 font = gFont("Regular", 20)
215                 offset = eSize(20, 5)
216                 
217                 for title in elementsWithTag(windowstyle.childNodes, "title"):
218                         offset = parseSize(title.getAttribute("offset"))
219                         font = parseFont(str(title.getAttribute("font")))
220
221                 style.setTitleFont(font);
222                 style.setTitleOffset(offset)
223                 
224                 for borderset in elementsWithTag(windowstyle.childNodes, "borderset"):
225                         bsName = str(borderset.getAttribute("name"))
226                         for pixmap in elementsWithTag(borderset.childNodes, "pixmap"):
227                                 bpName = str(pixmap.getAttribute("pos"))
228                                 filename = str(pixmap.getAttribute("filename"))
229                                 
230                                 png = loadPixmap(resolveFilename(SCOPE_SKIN_IMAGE, filename, path_prefix=path_prefix))
231                                 
232                                 # adapt palette
233                                 desktop.makeCompatiblePixmap(png.__deref__())
234                                 style.setPixmap(eWindowStyleSkinned.__dict__[bsName], eWindowStyleSkinned.__dict__[bpName], png.__deref__())
235
236                 for color in elementsWithTag(windowstyle.childNodes, "color"):
237                         type = str(color.getAttribute("name"))
238                         color = parseColor(color.getAttribute("color"))
239                         
240                         try:
241                                 style.setColor(eWindowStyleSkinned.__dict__["col" + type], color)
242                         except:
243                                 raise ("Unknown color %s" % (type))
244                         
245                 x = eWindowStyleManagerPtr()
246                 eWindowStyleManager.getInstance(x)
247                 x.setStyle(style)
248
249 def loadSkinData(desktop):
250         skins = dom_skins[:]
251         skins.reverse()
252         for (path, dom_skin) in skins:
253                 loadSingleSkinData(desktop, dom_skin, path)
254
255 def lookupScreen(name):
256         for (path, dom_skin) in dom_skins:
257                 # first, find the corresponding screen element
258                 skin = dom_skin.childNodes[0] 
259                 for x in elementsWithTag(skin.childNodes, "screen"):
260                         if x.getAttribute('name') == name:
261                                 return x, path
262         return None, None
263
264 def readSkin(screen, skin, name, desktop):
265         myscreen, path = lookupScreen(name)
266         
267         # otherwise try embedded skin
268         myscreen = myscreen or getattr(screen, "parsedSkin", None)
269         
270         # try uncompiled embedded skin
271         if myscreen is None and getattr(screen, "skin", None):
272                 myscreen = screen.parsedSkin = xml.dom.minidom.parseString(screen.skin).childNodes[0]
273         
274         assert myscreen is not None, "no skin for screen '" + name + "' found!"
275
276         screen.skinAttributes = [ ]
277         
278         skin_path_prefix = getattr(screen, "skin_path", path)
279
280         collectAttributes(screen.skinAttributes, myscreen, skin_path_prefix, ignore=["name"])
281         
282         screen.additionalWidgets = [ ]
283         screen.renderer = [ ]
284         
285         # now walk all widgets
286         for widget in elementsWithTag(myscreen.childNodes, "widget"):
287                 # ok, we either have 1:1-mapped widgets ('old style'), or 1:n-mapped 
288                 # widgets (source->renderer).
289
290                 wname = widget.getAttribute('name')
291                 wsource = widget.getAttribute('source')
292                 
293                 if wname is None and wsource is None:
294                         print "widget has no name and no source!"
295                         continue
296                 
297                 if wname:
298                         # get corresponding 'gui' object
299                         try:
300                                 attributes = screen[wname].skinAttributes = [ ]
301                         except:
302                                 raise str("component with name '" + wname + "' was not found in skin of screen '" + name + "'!")
303
304 #                       assert screen[wname] is not Source
305                 
306                         # and collect attributes for this
307                         collectAttributes(attributes, widget, skin_path_prefix, ignore=['name'])
308                 elif wsource:
309                         # get corresponding source
310                         source = screen.get(wsource)
311                         if source is None:
312                                 raise str("source '" + wsource + "' was not found in screen '" + name + "'!")
313                         
314                         wrender = widget.getAttribute('render')
315                         
316                         for converter in elementsWithTag(widget.childNodes, "convert"):
317                                 ctype = converter.getAttribute('type')
318                                 assert ctype
319                                 converter_class = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
320                                 parms = mergeText(converter.childNodes).strip()
321                                 c = converter_class(parms)
322                                 
323                                 c.connect(source)
324                                 source = c
325                         
326                         renderer_class = my_import('.'.join(["Components", "Renderer", wrender])).__dict__.get(wrender)
327                         
328                         renderer = renderer_class() # instantiate renderer
329                         
330                         renderer.connect(source) # connect to source
331                         attributes = renderer.skinAttributes = [ ]
332                         collectAttributes(attributes, widget, skin_path_prefix, ignore=['render', 'source'])
333                         
334                         screen.renderer.append(renderer)
335
336         # now walk additional objects
337         for widget in elementsWithTag(myscreen.childNodes, lambda x: x != "widget"):
338                 if widget.tagName == "applet":
339                         codeText = mergeText(widget.childNodes).strip()
340                         type = widget.getAttribute('type')
341
342                         code = compile(codeText, "skin applet", "exec")
343                         
344                         if type == "onLayoutFinish":
345                                 screen.onLayoutFinish.append(code)
346                         else:
347                                 raise str("applet type '%s' unknown!" % type)
348                         
349                         continue
350                 
351                 class additionalWidget:
352                         pass
353                 
354                 w = additionalWidget()
355                 
356                 if widget.tagName == "eLabel":
357                         w.widget = eLabel
358                 elif widget.tagName == "ePixmap":
359                         w.widget = ePixmap
360                 else:
361                         raise str("unsupported stuff : %s" % widget.tagName)
362                 
363                 w.skinAttributes = [ ]
364                 collectAttributes(w.skinAttributes, widget, skin_path_prefix)
365                 
366                 # applyAttributes(guiObject, widget, desktop)
367                 # guiObject.thisown = 0
368                 screen.additionalWidgets.append(w)