Merge pull request #4804 from jmarshallnz/constantly_repeating
[vuplus_xbmc] / xbmc / guilib / GUIIncludes.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIIncludes.h"
22 #include "addons/Skin.h"
23 #include "GUIInfoManager.h"
24 #include "utils/log.h"
25 #include "utils/XBMCTinyXML.h"
26 #include "utils/StringUtils.h"
27 #include "interfaces/info/SkinVariable.h"
28
29 using namespace std;
30
31 CGUIIncludes::CGUIIncludes()
32 {
33   m_constantAttributes.insert("x");
34   m_constantAttributes.insert("y");
35   m_constantAttributes.insert("width");
36   m_constantAttributes.insert("height");
37   m_constantAttributes.insert("center");
38   m_constantAttributes.insert("max");
39   m_constantAttributes.insert("min");
40   m_constantAttributes.insert("w");
41   m_constantAttributes.insert("h");
42   m_constantAttributes.insert("time");
43   m_constantAttributes.insert("acceleration");
44   m_constantAttributes.insert("delay");
45   m_constantAttributes.insert("start");
46   m_constantAttributes.insert("end");
47   m_constantAttributes.insert("center");
48   m_constantAttributes.insert("border");
49   m_constantAttributes.insert("repeat");
50   
51   m_constantNodes.insert("posx");
52   m_constantNodes.insert("posy");
53   m_constantNodes.insert("left");
54   m_constantNodes.insert("centerleft");
55   m_constantNodes.insert("right");
56   m_constantNodes.insert("centerright");
57   m_constantNodes.insert("top");
58   m_constantNodes.insert("centertop");
59   m_constantNodes.insert("bottom");
60   m_constantNodes.insert("centerbottom");
61   m_constantNodes.insert("width");
62   m_constantNodes.insert("height");
63   m_constantNodes.insert("offsetx");
64   m_constantNodes.insert("offsety");
65   m_constantNodes.insert("textoffsetx");
66   m_constantNodes.insert("textoffsety");  
67   m_constantNodes.insert("textwidth");
68   m_constantNodes.insert("spinposx");
69   m_constantNodes.insert("spinposy");
70   m_constantNodes.insert("spinwidth");
71   m_constantNodes.insert("spinheight");
72   m_constantNodes.insert("radioposx");
73   m_constantNodes.insert("radioposy");
74   m_constantNodes.insert("radiowidth");
75   m_constantNodes.insert("radioheight");
76   m_constantNodes.insert("markwidth");
77   m_constantNodes.insert("markheight");
78   m_constantNodes.insert("sliderwidth");
79   m_constantNodes.insert("sliderheight");
80   m_constantNodes.insert("itemgap");
81   m_constantNodes.insert("bordersize");
82   m_constantNodes.insert("timeperimage");
83   m_constantNodes.insert("fadetime");
84   m_constantNodes.insert("pauseatend");
85 }
86
87 CGUIIncludes::~CGUIIncludes()
88 {
89 }
90
91 void CGUIIncludes::ClearIncludes()
92 {
93   m_includes.clear();
94   m_defaults.clear();
95   m_constants.clear();
96   m_skinvariables.clear();
97   m_files.clear();
98 }
99
100 bool CGUIIncludes::LoadIncludes(const CStdString &includeFile)
101 {
102   // check to see if we already have this loaded
103   if (HasIncludeFile(includeFile))
104     return true;
105
106   CXBMCTinyXML doc;
107   if (!doc.LoadFile(includeFile))
108   {
109     CLog::Log(LOGINFO, "Error loading includes.xml file (%s): %s (row=%i, col=%i)", includeFile.c_str(), doc.ErrorDesc(), doc.ErrorRow(), doc.ErrorCol());
110     return false;
111   }
112   // success, load the tags
113   if (LoadIncludesFromXML(doc.RootElement()))
114   {
115     m_files.push_back(includeFile);
116     return true;
117   }
118   return false;
119 }
120
121 bool CGUIIncludes::LoadIncludesFromXML(const TiXmlElement *root)
122 {
123   if (!root || strcmpi(root->Value(), "includes"))
124   {
125     CLog::Log(LOGERROR, "Skin includes must start with the <includes> tag");
126     return false;
127   }
128   const TiXmlElement* node = root->FirstChildElement("include");
129   while (node)
130   {
131     if (node->Attribute("name") && node->FirstChild())
132     {
133       CStdString tagName = node->Attribute("name");
134       m_includes.insert(pair<CStdString, TiXmlElement>(tagName, *node));
135     }
136     else if (node->Attribute("file"))
137     { // load this file in as well
138       LoadIncludes(g_SkinInfo->GetSkinPath(node->Attribute("file")));
139     }
140     node = node->NextSiblingElement("include");
141   }
142   // now defaults
143   node = root->FirstChildElement("default");
144   while (node)
145   {
146     if (node->Attribute("type") && node->FirstChild())
147     {
148       CStdString tagName = node->Attribute("type");
149       m_defaults.insert(pair<CStdString, TiXmlElement>(tagName, *node));
150     }
151     node = node->NextSiblingElement("default");
152   }
153   // and finally constants
154   node = root->FirstChildElement("constant");
155   while (node)
156   {
157     if (node->Attribute("name") && node->FirstChild())
158     {
159       CStdString tagName = node->Attribute("name");
160       m_constants.insert(make_pair(tagName, node->FirstChild()->ValueStr()));
161     }
162     node = node->NextSiblingElement("constant");
163   }
164
165   node = root->FirstChildElement("variable");
166   while (node)
167   {
168     if (node->Attribute("name") && node->FirstChild())
169     {
170       CStdString tagName = node->Attribute("name");
171       m_skinvariables.insert(make_pair(tagName, *node));
172     }
173     node = node->NextSiblingElement("variable");
174   }
175
176   return true;
177 }
178
179 bool CGUIIncludes::HasIncludeFile(const CStdString &file) const
180 {
181   for (iFiles it = m_files.begin(); it != m_files.end(); ++it)
182     if (*it == file) return true;
183   return false;
184 }
185
186 void CGUIIncludes::ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
187 {
188   if (!node)
189     return;
190   ResolveIncludesForNode(node, xmlIncludeConditions);
191
192   TiXmlElement *child = node->FirstChildElement();
193   while (child)
194   {
195     ResolveIncludes(child, xmlIncludeConditions);
196     child = child->NextSiblingElement();
197   }
198 }
199
200 void CGUIIncludes::ResolveIncludesForNode(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
201 {
202   // we have a node, find any <include file="fileName">tagName</include> tags and replace
203   // recursively with their real includes
204   if (!node) return;
205
206   // First add the defaults if this is for a control
207   CStdString type;
208   if (node->ValueStr() == "control")
209   {
210     type = node->Attribute("type");
211     map<CStdString, TiXmlElement>::const_iterator it = m_defaults.find(type);
212     if (it != m_defaults.end())
213     {
214       // we don't insert <left> et. al. if <posx> or <posy> is specified
215       bool hasPosX(node->FirstChild("posx") != NULL);
216       bool hasPosY(node->FirstChild("posy") != NULL);
217
218       const TiXmlElement &element = (*it).second;
219       const TiXmlElement *tag = element.FirstChildElement();
220       while (tag)
221       {
222         std::string value = tag->ValueStr();
223         bool skip(false);
224         if (hasPosX && (value == "left" || value == "right" || value == "centerleft" || value == "centerright"))
225           skip = true;
226         if (hasPosY && (value == "top" || value == "bottom" || value == "centertop" || value == "centerbottom"))
227           skip = true;
228         // we insert at the end of block
229         if (!skip)
230           node->InsertEndChild(*tag);
231         tag = tag->NextSiblingElement();
232       }
233     }
234   }
235
236   TiXmlElement *include = node->FirstChildElement("include");
237   while (include && include->FirstChild())
238   {
239     // have an include tag - grab it's tag name and replace it with the real tag contents
240     const char *file = include->Attribute("file");
241     if (file)
242     { // we need to load this include from the alternative file
243       LoadIncludes(g_SkinInfo->GetSkinPath(file));
244     }
245     const char *condition = include->Attribute("condition");
246     if (condition)
247     { // check this condition
248       INFO::InfoPtr conditionID = g_infoManager.Register(condition);
249       bool value = conditionID->Get();
250
251       if (xmlIncludeConditions)
252         (*xmlIncludeConditions)[conditionID] = value;
253
254       if (!value)
255       {
256         include = include->NextSiblingElement("include");
257         continue;
258       }
259     }
260     CStdString tagName = include->FirstChild()->Value();
261     map<CStdString, TiXmlElement>::const_iterator it = m_includes.find(tagName);
262     if (it != m_includes.end())
263     { // found the tag(s) to include - let's replace it
264       const TiXmlElement &element = (*it).second;
265       const TiXmlElement *tag = element.FirstChildElement();
266       while (tag)
267       {
268         // we insert before the <include> element to keep the correct
269         // order (we render in the order given in the xml file)
270         node->InsertBeforeChild(include, *tag);
271         tag = tag->NextSiblingElement();
272       }
273       // remove the <include>tagName</include> element
274       node->RemoveChild(include);
275       include = node->FirstChildElement("include");
276     }
277     else
278     { // invalid include
279       CLog::Log(LOGWARNING, "Skin has invalid include: %s", tagName.c_str());
280       include = include->NextSiblingElement("include");
281     }
282   }
283
284   // run through this element's attributes, resolving any constants
285   TiXmlAttribute *attribute = node->FirstAttribute();
286   while (attribute)
287   { // check the attribute against our set
288     if (m_constantAttributes.count(attribute->Name()))
289       attribute->SetValue(ResolveConstant(attribute->ValueStr()));
290     attribute = attribute->Next();
291   }
292   // also do the value
293   if (node->FirstChild() && node->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT && m_constantNodes.count(node->ValueStr()))
294     node->FirstChild()->SetValue(ResolveConstant(node->FirstChild()->ValueStr()));
295 }
296
297 CStdString CGUIIncludes::ResolveConstant(const CStdString &constant) const
298 {
299   CStdStringArray values;
300   StringUtils::SplitString(constant, ",", values);
301   for (unsigned int i = 0; i < values.size(); ++i)
302   {
303     map<CStdString, CStdString>::const_iterator it = m_constants.find(values[i]);
304     if (it != m_constants.end())
305       values[i] = it->second;
306   }
307   CStdString value;
308   StringUtils::JoinString(values, ",", value);
309   return value;
310 }
311
312 const INFO::CSkinVariableString* CGUIIncludes::CreateSkinVariable(const CStdString& name, int context)
313 {
314   map<CStdString, TiXmlElement>::const_iterator it = m_skinvariables.find(name);
315   if (it != m_skinvariables.end())
316     return INFO::CSkinVariable::CreateFromXML(it->second, context);
317   return NULL;
318 }