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