]> git.sesse.net Git - vlc/blob - modules/gui/skins2/parser/skin_parser.cpp
* skins2:
[vlc] / modules / gui / skins2 / parser / skin_parser.cpp
1 /*****************************************************************************
2  * skin_parser.cpp
3  *****************************************************************************
4  * Copyright (C) 2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #include "skin_parser.hpp"
25 #include "../src/os_factory.hpp"
26 #include "interpreter.hpp"
27 #include <math.h>
28
29 SkinParser::SkinParser( intf_thread_t *pIntf, const string &rFileName,
30                         const string &rPath, bool useDTD, BuilderData *pData ):
31     XMLParser( pIntf, rFileName, useDTD ), m_path( rPath), m_pData(pData),
32     m_ownData(pData == NULL), m_xOffset( 0 ), m_yOffset( 0 )
33 {
34     // Make sure the data is allocated
35     if( m_pData == NULL )
36     {
37         m_pData = new BuilderData();
38     }
39
40     // Special id, we don't want any control to have the same one
41     m_idSet.insert( "none" );
42     // At the beginning, there is no Panel
43     m_panelStack.push_back( "none" );
44 }
45
46
47 SkinParser::~SkinParser()
48 {
49     if( m_ownData )
50     {
51         delete m_pData;
52     }
53 }
54
55
56 void SkinParser::handleBeginElement( const string &rName, AttrList_t &attr )
57 {
58 #define CheckDefault( a, b ) \
59     if( attr.find(a) == attr.end() ) attr[strdup(a)] = strdup(b);
60 #define RequireDefault( a ) \
61     if( attr.find(a) == attr.end() ) \
62     { \
63         msg_Err( getIntf(), "bad theme (element: %s, missing attribute: %s)", \
64                  rName.c_str(), a ); \
65         m_errors = true; return; \
66     }
67
68     if( rName == "Include" )
69     {
70         RequireDefault( "file" );
71
72         OSFactory *pFactory = OSFactory::instance( getIntf() );
73         string fullPath = m_path + pFactory->getDirSeparator() + attr["file"];
74         msg_Dbg( getIntf(), "opening included XML file: %s", fullPath.c_str() );
75         // FIXME: We do not use the DTD to validate the included XML file,
76         // as the parser seems to dislike it otherwise...
77         SkinParser subParser( getIntf(), fullPath.c_str(), false, m_pData );
78         subParser.parse();
79     }
80
81     else if( rName == "IniFile" )
82     {
83         RequireDefault( "id" );
84         RequireDefault( "file" );
85
86         const BuilderData::IniFile iniFile( uniqueId( attr["id"] ),
87                 attr["file"] );
88         m_pData->m_listIniFile.push_back( iniFile );
89     }
90
91     else if( rName == "Anchor" )
92     {
93         RequireDefault( "priority" );
94         CheckDefault( "x", "0" );
95         CheckDefault( "y", "0" );
96         CheckDefault( "lefttop", "lefttop" );
97         CheckDefault( "points", "(0,0)" );
98         CheckDefault( "range", "10" );
99
100         const BuilderData::Anchor anchor( atoi( attr["x"] ) + m_xOffset,
101                 atoi( attr["y"] ) + m_yOffset, attr["lefttop"],
102                 atoi( attr["range"] ), atoi( attr["priority"] ),
103                 attr["points"], m_curLayoutId );
104         m_pData->m_listAnchor.push_back( anchor );
105     }
106
107     else if( rName == "Bitmap" )
108     {
109         RequireDefault( "id" );
110         RequireDefault( "file" );
111         RequireDefault( "alphacolor" );
112         CheckDefault( "nbframes", "1" );
113         CheckDefault( "fps", "4" );
114
115         m_curBitmapId = uniqueId( attr["id"] );
116         const BuilderData::Bitmap bitmap( m_curBitmapId,
117                 attr["file"], convertColor( attr["alphacolor"] ),
118                 atoi( attr["nbframes"] ), atoi( attr["fps"] ) );
119         m_pData->m_listBitmap.push_back( bitmap );
120     }
121
122     else if( rName == "SubBitmap" )
123     {
124         RequireDefault( "id" );
125         RequireDefault( "x" );
126         RequireDefault( "y" );
127         RequireDefault( "width" );
128         RequireDefault( "height" );
129         CheckDefault( "nbframes", "1" );
130         CheckDefault( "fps", "4" );
131
132         const BuilderData::SubBitmap bitmap( uniqueId( attr["id"] ),
133                 m_curBitmapId, atoi( attr["x"] ), atoi( attr["y"] ),
134                 atoi( attr["width"] ), atoi( attr["height"] ),
135                 atoi( attr["nbframes"] ), atoi( attr["fps"] ) );
136         m_pData->m_listSubBitmap.push_back( bitmap );
137     }
138
139     else if( rName == "BitmapFont" )
140     {
141         RequireDefault( "id" );
142         RequireDefault( "file" );
143         CheckDefault( "type", "digits" );
144
145         const BuilderData::BitmapFont font( uniqueId( attr["id"] ),
146                 attr["file"], attr["type"] );
147         m_pData->m_listBitmapFont.push_back( font );
148     }
149
150     else if( rName == "PopupMenu" )
151     {
152         RequireDefault( "id" );
153
154         m_popupPosList.push_back(0);
155         m_curPopupId = uniqueId( attr["id"] );
156         const BuilderData::PopupMenu popup( m_curPopupId );
157         m_pData->m_listPopupMenu.push_back( popup );
158     }
159
160     else if( rName == "MenuItem" )
161     {
162         RequireDefault( "label" );
163         CheckDefault( "action", "none" );
164
165         const BuilderData::MenuItem item( attr["label"], attr["action"],
166                                           m_popupPosList.back(),
167                                           m_curPopupId );
168         m_pData->m_listMenuItem.push_back( item );
169         m_popupPosList.back()++;
170     }
171
172     else if( rName == "MenuSeparator" )
173     {
174         const BuilderData::MenuSeparator sep( m_popupPosList.back(),
175                                               m_curPopupId );
176         m_pData->m_listMenuSeparator.push_back( sep );
177         m_popupPosList.back()++;
178     }
179
180     else if( rName == "Button" )
181     {
182         RequireDefault( "up" );
183         CheckDefault( "id", "none" );
184         CheckDefault( "visible", "true" );
185         CheckDefault( "x", "0" );
186         CheckDefault( "y", "0" );
187         CheckDefault( "lefttop", "lefttop" );
188         CheckDefault( "rightbottom", "lefttop" );
189         CheckDefault( "xkeepratio", "false" );
190         CheckDefault( "ykeepratio", "false" );
191         CheckDefault( "down", "none" );
192         CheckDefault( "over", "none" );
193         CheckDefault( "action", "none" );
194         CheckDefault( "tooltiptext", "" );
195         CheckDefault( "help", "" );
196
197         const BuilderData::Button button( uniqueId( attr["id"] ),
198                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
199                 attr["lefttop"], attr["rightbottom"],
200                 convertBoolean( attr["xkeepratio"] ),
201                 convertBoolean( attr["ykeepratio"] ), attr["visible"],
202                 attr["up"], attr["down"], attr["over"], attr["action"],
203                 attr["tooltiptext"], attr["help"],
204                 m_curLayer, m_curWindowId, m_curLayoutId, m_panelStack.back() );
205         m_curLayer++;
206         m_pData->m_listButton.push_back( button );
207     }
208
209     else if( rName == "Checkbox" )
210     {
211         RequireDefault( "up1" );
212         RequireDefault( "up2" );
213         RequireDefault( "state" );
214         CheckDefault( "id", "none" );
215         CheckDefault( "visible", "true" );
216         CheckDefault( "x", "0" );
217         CheckDefault( "y", "0" );
218         CheckDefault( "lefttop", "lefttop" );
219         CheckDefault( "rightbottom", "lefttop" );
220         CheckDefault( "xkeepratio", "false" );
221         CheckDefault( "ykeepratio", "false" );
222         CheckDefault( "down1", "none" );
223         CheckDefault( "over1", "none" );
224         CheckDefault( "down2", "none" );
225         CheckDefault( "over2", "none" );
226         CheckDefault( "action1", "none" );
227         CheckDefault( "action2", "none" );
228         CheckDefault( "tooltiptext1", "" );
229         CheckDefault( "tooltiptext2", "" );
230         CheckDefault( "help", "" );
231
232         const BuilderData::Checkbox checkbox( uniqueId( attr["id"] ),
233                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
234                 attr["lefttop"], attr["rightbottom"],
235                 convertBoolean( attr["xkeepratio"] ),
236                 convertBoolean( attr["ykeepratio"] ), attr["visible"],
237                 attr["up1"], attr["down1"], attr["over1"],
238                 attr["up2"], attr["down2"], attr["over2"], attr["state"],
239                 attr["action1"], attr["action2"], attr["tooltiptext1"],
240                 attr["tooltiptext2"], attr["help"], m_curLayer, m_curWindowId,
241                 m_curLayoutId, m_panelStack.back() );
242         m_curLayer++;
243         m_pData->m_listCheckbox.push_back( checkbox );
244     }
245
246     else if( rName == "Font" )
247     {
248         RequireDefault( "id" );
249         RequireDefault( "file" );
250         CheckDefault( "size", "12" );
251
252         const BuilderData::Font fontData( uniqueId( attr["id"] ),
253                 attr["file"], atoi( attr["size"] ) );
254         m_pData->m_listFont.push_back( fontData );
255     }
256
257     else if( rName == "Group" )
258     {
259         CheckDefault( "x", "0" );
260         CheckDefault( "y", "0" );
261
262         m_xOffset += atoi( attr["x"] );
263         m_yOffset += atoi( attr["y"] );
264         m_xOffsetList.push_back( atoi( attr["x"] ) );
265         m_yOffsetList.push_back( atoi( attr["y"] ) );
266     }
267
268     else if( rName == "Image" )
269     {
270         RequireDefault( "image" );
271         CheckDefault( "id", "none" );
272         CheckDefault( "visible", "true" );
273         CheckDefault( "x", "0" );
274         CheckDefault( "y", "0" );
275         CheckDefault( "lefttop", "lefttop" );
276         CheckDefault( "rightbottom", "lefttop" );
277         CheckDefault( "xkeepratio", "false" );
278         CheckDefault( "ykeepratio", "false" );
279         CheckDefault( "action", "none" );
280         CheckDefault( "action2", "none" );
281         CheckDefault( "resize", "mosaic" );
282         CheckDefault( "help", "" );
283
284         const BuilderData::Image imageData( uniqueId( attr["id"] ),
285                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
286                 attr["lefttop"], attr["rightbottom"],
287                 convertBoolean( attr["xkeepratio"] ),
288                 convertBoolean( attr["ykeepratio"] ), attr["visible"],
289                 attr["image"], attr["action"], attr["action2"], attr["resize"],
290                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId,
291                 m_panelStack.back() );
292         m_curLayer++;
293         m_pData->m_listImage.push_back( imageData );
294     }
295
296     else if( rName == "Layout" )
297     {
298         RequireDefault( "width" );
299         RequireDefault( "height" );
300         CheckDefault( "id", "none" );
301         CheckDefault( "minwidth", "-1" );
302         CheckDefault( "maxwidth", "-1" );
303         CheckDefault( "minheight", "-1" );
304         CheckDefault( "maxheight", "-1" );
305
306         m_curLayoutId = uniqueId( attr["id"] );
307         const BuilderData::Layout layout( m_curLayoutId, atoi( attr["width"] ),
308                 atoi( attr["height"] ), atoi( attr["minwidth"] ),
309                 atoi( attr["maxwidth"] ), atoi( attr["minheight"] ),
310                 atoi( attr["maxheight"] ), m_curWindowId );
311         m_pData->m_listLayout.push_back( layout );
312         m_curLayer = 0;
313     }
314
315     else if( rName == "Panel" )
316     {
317         CheckDefault( "x", "0" );
318         CheckDefault( "y", "0" );
319         CheckDefault( "lefttop", "lefttop" );
320         CheckDefault( "rightbottom", "lefttop" );
321         CheckDefault( "xkeepratio", "false" );
322         CheckDefault( "ykeepratio", "false" );
323         RequireDefault( "width" );
324         RequireDefault( "height" );
325
326         string panelId = uniqueId( "none" );
327         const BuilderData::Panel panel( panelId,
328                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
329                 attr["lefttop"], attr["rightbottom"],
330                 convertBoolean( attr["xkeepratio"] ),
331                 convertBoolean( attr["ykeepratio"] ),
332                 atoi( attr["width"] ), atoi( attr["height" ] ),
333                 m_curLayer, m_curWindowId, m_curLayoutId, m_panelStack.back() );
334         m_curLayer++;
335         m_pData->m_listPanel.push_back( panel );
336         // Add the panel to the stack
337         m_panelStack.push_back( panelId );
338     }
339
340     else if( rName == "Playlist" )
341     {
342         RequireDefault( "id" );
343         RequireDefault( "font" );
344         CheckDefault( "visible", "true" );
345         CheckDefault( "flat", "true" ); // Only difference here
346         CheckDefault( "x", "0" );
347         CheckDefault( "y", "0" );
348         CheckDefault( "width", "0" );
349         CheckDefault( "height", "0" );
350         CheckDefault( "lefttop", "lefttop" );
351         CheckDefault( "rightbottom", "lefttop" );
352         CheckDefault( "xkeepratio", "false" );
353         CheckDefault( "ykeepratio", "false" );
354         CheckDefault( "bgimage", "none" );
355         CheckDefault( "itemimage", "none" );
356         CheckDefault( "openimage", "none" );
357         CheckDefault( "closedimage", "none" );
358         CheckDefault( "fgcolor", "#000000" );
359         CheckDefault( "playcolor", "#FF0000" );
360         CheckDefault( "bgcolor1", "#FFFFFF" );
361         CheckDefault( "bgcolor2", "#FFFFFF" );
362         CheckDefault( "selcolor", "#0000FF" );
363         CheckDefault( "help", "" );
364
365         m_curTreeId = uniqueId( attr["id"] );
366         const BuilderData::Tree treeData( m_curTreeId, atoi( attr["x"] ) +
367                 m_xOffset, atoi( attr["y"] ) + m_yOffset, attr["visible"],
368                 attr["flat"],
369                 atoi( attr["width"]), atoi( attr["height"] ),
370                 attr["lefttop"], attr["rightbottom"],
371                 convertBoolean( attr["xkeepratio"] ),
372                 convertBoolean( attr["ykeepratio"] ),
373                 attr["font"], "playtree",
374                 attr["bgimage"], attr["itemimage"],
375                 attr["openimage"], attr["closedimage"],
376                 attr["fgcolor"],
377                 attr["playcolor"],
378                 attr["bgcolor1"],
379                 attr["bgcolor2"],
380                 attr["selcolor"], attr["help"],
381                 m_curLayer, m_curWindowId, m_curLayoutId, m_panelStack.back() );
382         m_curLayer++;
383         m_pData->m_listTree.push_back( treeData );
384     }
385     else if( rName == "Playtree" )
386     {
387         RequireDefault( "id" );
388         RequireDefault( "font" );
389         CheckDefault( "visible", "true" );
390         CheckDefault( "flat", "false" );
391         CheckDefault( "x", "0" );
392         CheckDefault( "y", "0" );
393         CheckDefault( "width", "0" );
394         CheckDefault( "height", "0" );
395         CheckDefault( "lefttop", "lefttop" );
396         CheckDefault( "rightbottom", "lefttop" );
397         CheckDefault( "xkeepratio", "false" );
398         CheckDefault( "ykeepratio", "false" );
399         CheckDefault( "bgimage", "none" );
400         CheckDefault( "itemimage", "none" );
401         CheckDefault( "openimage", "none" );
402         CheckDefault( "closedimage", "none" );
403         CheckDefault( "fgcolor", "#000000" );
404         CheckDefault( "playcolor", "#FF0000" );
405         CheckDefault( "bgcolor1", "#FFFFFF" );
406         CheckDefault( "bgcolor2", "#FFFFFF" );
407         CheckDefault( "selcolor", "#0000FF" );
408         CheckDefault( "help", "" );
409
410         m_curTreeId = uniqueId( attr["id"] );
411         const BuilderData::Tree treeData( m_curTreeId, atoi( attr["x"] ) +
412                 m_xOffset, atoi( attr["y"] ) + m_yOffset, attr["visible"],
413                 attr["flat"],
414                 atoi( attr["width"]), atoi( attr["height"] ),
415                 attr["lefttop"], attr["rightbottom"],
416                 convertBoolean( attr["xkeepratio"] ),
417                 convertBoolean( attr["ykeepratio"] ),
418                 attr["font"], "playtree",
419                 attr["bgimage"], attr["itemimage"],
420                 attr["openimage"], attr["closedimage"],
421                 attr["fgcolor"], attr["playcolor"],
422                 attr["bgcolor1"], attr["bgcolor2"],
423                 attr["selcolor"], attr["help"],
424                 m_curLayer, m_curWindowId, m_curLayoutId, m_panelStack.back() );
425         m_curLayer++;
426         m_pData->m_listTree.push_back( treeData );
427     }
428
429     else if( rName == "RadialSlider" )
430     {
431         RequireDefault( "sequence" );
432         RequireDefault( "nbimages" );
433         CheckDefault( "id", "none" );
434         CheckDefault( "visible", "true" );
435         CheckDefault( "x", "0" );
436         CheckDefault( "y", "0" );
437         CheckDefault( "lefttop", "lefttop" );
438         CheckDefault( "rightbottom", "lefttop" );
439         CheckDefault( "xkeepratio", "false" );
440         CheckDefault( "ykeepratio", "false" );
441         CheckDefault( "minangle", "0" );
442         CheckDefault( "maxangle", "360" );
443         CheckDefault( "value", "none" );
444         CheckDefault( "tooltiptext", "" );
445         CheckDefault( "help", "" );
446
447         const BuilderData::RadialSlider radial( uniqueId( attr["id"] ),
448                 attr["visible"],
449                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
450                 attr["lefttop"], attr["rightbottom"],
451                 convertBoolean( attr["xkeepratio"] ),
452                 convertBoolean( attr["ykeepratio"] ), attr["sequence"],
453                 atoi( attr["nbImages"] ), atof( attr["minAngle"] ) * M_PI /180,
454                 atof( attr["maxAngle"] ) * M_PI / 180, attr["value"],
455                 attr["tooltiptext"], attr["help"], m_curLayer, m_curWindowId,
456                 m_curLayoutId, m_panelStack.back() );
457         m_curLayer++;
458         m_pData->m_listRadialSlider.push_back( radial );
459     }
460
461     else if( rName == "Slider" )
462     {
463         RequireDefault( "up" );
464         RequireDefault( "points" );
465         CheckDefault( "id", "none" );
466         CheckDefault( "visible", "true" );
467         CheckDefault( "x", "0" );
468         CheckDefault( "y", "0" );
469         CheckDefault( "lefttop", "lefttop" );
470         CheckDefault( "rightbottom", "lefttop" );
471         CheckDefault( "xkeepratio", "false" );
472         CheckDefault( "ykeepratio", "false" );
473         CheckDefault( "down", "none" );
474         CheckDefault( "over", "none" );
475         CheckDefault( "thickness", "10" );
476         CheckDefault( "value", "none" );
477         CheckDefault( "tooltiptext", "" );
478         CheckDefault( "help", "" );
479
480         string newValue = attr["value"];
481         if( m_curTreeId != "" )
482         {
483             // Slider associated to a tree
484             newValue = "playtree.slider";
485         }
486         const BuilderData::Slider slider( uniqueId( attr["id"] ),
487                 attr["visible"], atoi( attr["x"] ) + m_xOffset,
488                 atoi( attr["y"] ) + m_yOffset, attr["lefttop"],
489                 attr["rightbottom"], convertBoolean( attr["xkeepratio"] ),
490                 convertBoolean( attr["ykeepratio"] ), attr["up"], attr["down"],
491                 attr["over"], attr["points"], atoi( attr["thickness"] ),
492                 newValue, "none", 0, 0, 0, 0, attr["tooltiptext"],
493                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId,
494                 m_panelStack.back() );
495         m_curLayer++;
496         m_pData->m_listSlider.push_back( slider );
497     }
498
499     else if( rName == "SliderBackground" )
500     {
501         RequireDefault( "image" );
502         CheckDefault( "nbhoriz", "1" );
503         CheckDefault( "nbvert", "1" );
504         CheckDefault( "padhoriz", "0" );
505         CheckDefault( "padvert", "0" );
506
507         // Retrieve the current slider data
508         BuilderData::Slider &slider = m_pData->m_listSlider.back();
509
510         slider.m_imageId = attr["image"];
511         slider.m_nbHoriz = atoi( attr["nbhoriz"] );
512         slider.m_nbVert = atoi( attr["nbvert"] );
513         slider.m_padHoriz = atoi( attr["padhoriz"] );
514         slider.m_padVert = atoi( attr["padvert"] );
515     }
516
517     else if( rName == "Text" )
518     {
519         RequireDefault( "font" );
520         CheckDefault( "id", "none" );
521         CheckDefault( "visible", "true" );
522         CheckDefault( "x", "0" );
523         CheckDefault( "y", "0" );
524         CheckDefault( "text", "" );
525         CheckDefault( "color", "#000000" );
526         CheckDefault( "scrolling", "auto" );
527         CheckDefault( "alignment", "left" );
528         CheckDefault( "width", "0" );
529         CheckDefault( "lefttop", "lefttop" );
530         CheckDefault( "rightbottom", "lefttop" );
531         CheckDefault( "xkeepratio", "false" );
532         CheckDefault( "ykeepratio", "false" );
533         CheckDefault( "help", "" );
534
535         const BuilderData::Text textData( uniqueId( attr["id"] ),
536                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
537                 attr["visible"], attr["font"],
538                 attr["text"], atoi( attr["width"] ),
539                 attr["lefttop"], attr["rightbottom"],
540                 convertBoolean( attr["xkeepratio"] ),
541                 convertBoolean( attr["ykeepratio"] ),
542                 convertColor( attr["color"] ),
543                 attr["scrolling"], attr["alignment"],
544                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId,
545                 m_panelStack.back() );
546         m_curLayer++;
547         m_pData->m_listText.push_back( textData );
548     }
549
550     else if( rName == "Theme" )
551     {
552         RequireDefault( "version" );
553         CheckDefault( "tooltipfont", "defaultfont" );
554         CheckDefault( "magnet", "15" );
555         CheckDefault( "alpha", "255" );
556         CheckDefault( "movealpha", "255" );
557
558         // Check the version
559         if( strcmp( attr["version"], SKINS_DTD_VERSION ) )
560         {
561             msg_Err( getIntf(), "bad theme version : %s (you need version %s)",
562                      attr["version"], SKINS_DTD_VERSION );
563             m_errors = true;
564             return;
565         }
566         const BuilderData::Theme theme( attr["tooltipfont"],
567                 atoi( attr["magnet"] ),
568                 convertInRange( attr["alpha"], 1, 255, "alpha" ),
569                 convertInRange( attr["movealpha"], 1, 255, "movealpha" ) );
570         m_pData->m_listTheme.push_back( theme );
571     }
572
573     else if( rName == "ThemeInfo" )
574     {
575         CheckDefault( "name", "" );
576         CheckDefault( "author", "" );
577         CheckDefault( "email", "" );
578         CheckDefault( "website", "" );
579         msg_Info( getIntf(), "skin: %s  author: %s", attr["name"],
580                   attr["author"] );
581     }
582
583     else if( rName == "Video" )
584     {
585         CheckDefault( "id", "none" );
586         CheckDefault( "visible", "true" );
587         CheckDefault( "x", "0" );
588         CheckDefault( "y", "0" );
589         CheckDefault( "width", "0" );
590         CheckDefault( "height", "0" );
591         CheckDefault( "lefttop", "lefttop" );
592         CheckDefault( "rightbottom", "lefttop" );
593         CheckDefault( "xkeepratio", "false" );
594         CheckDefault( "ykeepratio", "false" );
595         CheckDefault( "autoresize", "false" );
596         CheckDefault( "help", "" );
597
598         const BuilderData::Video videoData( uniqueId( attr["id"] ),
599                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
600                 atoi( attr["width"] ), atoi( attr["height" ]),
601                 attr["lefttop"], attr["rightbottom"],
602                 convertBoolean( attr["xkeepratio"] ),
603                 convertBoolean( attr["ykeepratio"] ),
604                 attr["visible"], convertBoolean( attr["autoresize"] ),
605                 attr["help"], m_curLayer, m_curWindowId, m_curLayoutId,
606                 m_panelStack.back() );
607         m_curLayer++;
608         m_pData->m_listVideo.push_back( videoData );
609     }
610
611     else if( rName == "Window" )
612     {
613         CheckDefault( "id", "none" );
614         CheckDefault( "visible", "true" );
615         CheckDefault( "x", "0" );
616         CheckDefault( "y", "0" );
617         CheckDefault( "dragdrop", "true" );
618         CheckDefault( "playondrop", "true" );
619
620         m_curWindowId = uniqueId( attr["id"] );
621         const BuilderData::Window window( m_curWindowId,
622                 atoi( attr["x"] ) + m_xOffset, atoi( attr["y"] ) + m_yOffset,
623                 convertBoolean( attr["visible"] ),
624                 convertBoolean( attr["dragdrop"] ),
625                 convertBoolean( attr["playondrop"] ) );
626         m_pData->m_listWindow.push_back( window );
627     }
628 }
629
630
631 void SkinParser::handleEndElement( const string &rName )
632 {
633     if( rName == "Group" )
634     {
635         m_xOffset -= m_xOffsetList.back();
636         m_yOffset -= m_yOffsetList.back();
637         m_xOffsetList.pop_back();
638         m_yOffsetList.pop_back();
639     }
640     else if( rName == "Playtree" || rName == "Playlist" )
641     {
642         m_curTreeId = "";
643     }
644     else if( rName == "Popup" )
645     {
646         m_curPopupId = "";
647         m_popupPosList.pop_back();
648     }
649     else if( rName == "Panel" )
650     {
651         m_panelStack.pop_back();
652     }
653 }
654
655
656 bool SkinParser::convertBoolean( const char *value ) const
657 {
658     return strcmp( value, "true" ) == 0;
659 }
660
661
662 int SkinParser::convertColor( const char *transcolor )
663 {
664     // TODO: move to the builder
665     unsigned long iRed, iGreen, iBlue;
666     iRed = iGreen = iBlue = 0;
667     sscanf( transcolor, "#%2lX%2lX%2lX", &iRed, &iGreen, &iBlue );
668     return ( iRed << 16 | iGreen << 8 | iBlue );
669 }
670
671
672 int SkinParser::convertInRange( const char *value, int minValue, int maxValue,
673                                 const string &rAttribute ) const
674 {
675     int intValue = atoi( value );
676
677     if( intValue < minValue )
678     {
679         msg_Warn( getIntf(), "value of \"%s\" attribute (%i) is out of the "
680                   "expected range [%i, %i], using %i instead",
681                   rAttribute.c_str(), intValue, minValue, maxValue, minValue );
682         return minValue;
683     }
684     else if( intValue > maxValue )
685     {
686         msg_Warn( getIntf(), "value of \"%s\" attribute (%i) is out of the "
687                   "expected range [%i, %i], using %i instead",
688                   rAttribute.c_str(), intValue, minValue, maxValue, maxValue );
689         return maxValue;
690     }
691     else
692     {
693         return intValue;
694     }
695 }
696
697
698 const string SkinParser::generateId() const
699 {
700     static int i = 1;
701
702     char genId[5];
703     snprintf( genId, 4, "%i", i++ );
704
705     string base = "_ReservedId_" + (string)genId;
706
707     return base;
708 }
709
710
711 const string SkinParser::uniqueId( const string &id )
712 {
713     string newId;
714
715     if( m_idSet.find( id ) != m_idSet.end() )
716     {
717         // The id was already used
718         if( id != "none" )
719         {
720             msg_Warn( getIntf(), "non-unique id: %s", id.c_str() );
721         }
722         newId = generateId();
723     }
724     else
725     {
726         // OK, this is a new id
727         newId = id;
728     }
729
730     // Add the id to the set
731     m_idSet.insert( newId );
732
733     return newId;
734 }