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