Merge pull request #4676 from jmarshallnz/dont_set_scraper_on_tvshow_on_nfo
[vuplus_xbmc] / xbmc / addons / Skin.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 "Skin.h"
22 #include "AddonManager.h"
23 #include "LangInfo.h"
24 #include "Util.h"
25 #include "filesystem/File.h"
26 #include "filesystem/SpecialProtocol.h"
27 #include "guilib/WindowIDs.h"
28 #include "settings/Settings.h"
29 #include "settings/lib/Setting.h"
30 #include "utils/URIUtils.h"
31 #include "utils/log.h"
32 #include "utils/StringUtils.h"
33
34 // fallback for new skin resolution code
35 #include "filesystem/Directory.h"
36
37 using namespace std;
38 using namespace XFILE;
39
40 #define SKIN_MIN_VERSION 2.1f
41
42 boost::shared_ptr<ADDON::CSkinInfo> g_SkinInfo;
43
44 namespace ADDON
45 {
46
47 CSkinInfo::CSkinInfo(const AddonProps &props, const RESOLUTION_INFO &resolution)
48   : CAddon(props), m_defaultRes(resolution)
49 {
50 }
51
52 CSkinInfo::CSkinInfo(const cp_extension_t *ext)
53   : CAddon(ext)
54 {
55   ELEMENTS elements;
56   if (CAddonMgr::Get().GetExtElements(ext->configuration, "res", elements))
57   {
58     for (ELEMENTS::iterator i = elements.begin(); i != elements.end(); ++i)
59     {
60       int width = atoi(CAddonMgr::Get().GetExtValue(*i, "@width"));
61       int height = atoi(CAddonMgr::Get().GetExtValue(*i, "@height"));
62       bool defRes = CAddonMgr::Get().GetExtValue(*i, "@default").Equals("true");
63       CStdString folder = CAddonMgr::Get().GetExtValue(*i, "@folder");
64       float aspect = 0;
65       CStdStringArray fracs;
66       CStdString strAspect = CAddonMgr::Get().GetExtValue(*i, "@aspect");
67       StringUtils::SplitString(strAspect, ":", fracs);
68       if (fracs.size() == 2)
69         aspect = (float)(atof(fracs[0].c_str())/atof(fracs[1].c_str()));
70       if (width > 0 && height > 0)
71       {
72         RESOLUTION_INFO res(width, height, aspect, folder);
73         res.strId = strAspect; // for skin usage, store aspect string in strId
74         if (defRes)
75           m_defaultRes = res;
76         m_resolutions.push_back(res);
77       }
78     }
79   }
80   else
81   { // no resolutions specified -> backward compatibility
82     CStdString defaultWide = CAddonMgr::Get().GetExtValue(ext->configuration, "@defaultwideresolution");
83     if (defaultWide.empty())
84       defaultWide = CAddonMgr::Get().GetExtValue(ext->configuration, "@defaultresolution");
85     TranslateResolution(defaultWide, m_defaultRes);
86   }
87
88   CStdString str = CAddonMgr::Get().GetExtValue(ext->configuration, "@effectslowdown");
89   if (!str.empty())
90     m_effectsSlowDown = (float)atof(str.c_str());
91   else
92     m_effectsSlowDown = 1.f;
93
94   str = CAddonMgr::Get().GetExtValue(ext->configuration, "@debugging");
95   m_debugging = !strcmp(str.c_str(), "true");
96
97   LoadStartupWindows(ext);
98   m_Version = 2.11;
99 }
100
101 CSkinInfo::~CSkinInfo()
102 {
103 }
104
105 AddonPtr CSkinInfo::Clone() const
106 {
107   return AddonPtr(new CSkinInfo(*this));
108 }
109
110 struct closestRes
111 {
112   closestRes(const RESOLUTION_INFO &target) : m_target(target) { };
113   bool operator()(const RESOLUTION_INFO &i, const RESOLUTION_INFO &j)
114   {
115     float diff = fabs(i.DisplayRatio() - m_target.DisplayRatio()) - fabs(j.DisplayRatio() - m_target.DisplayRatio());
116     if (diff < 0) return true;
117     if (diff > 0) return false;
118     diff = fabs((float)i.iHeight - m_target.iHeight) - fabs((float)j.iHeight - m_target.iHeight);
119     if (diff < 0) return true;
120     if (diff > 0) return false;
121     return fabs((float)i.iWidth - m_target.iWidth) < fabs((float)j.iWidth - m_target.iWidth);
122   }
123   RESOLUTION_INFO m_target;
124 };
125
126 void CSkinInfo::Start()
127 {
128   if (!m_resolutions.size())
129   { // try falling back to whatever resolutions exist in the directory
130     CFileItemList items;
131     CDirectory::GetDirectory(Path(), items, "", DIR_FLAG_NO_FILE_DIRS);
132     for (int i = 0; i < items.Size(); i++)
133     {
134       RESOLUTION_INFO res;
135       if (items[i]->m_bIsFolder && TranslateResolution(items[i]->GetLabel(), res))
136         m_resolutions.push_back(res);
137     }
138   }
139
140   if (!m_resolutions.empty())
141   {
142     // find the closest resolution
143     const RESOLUTION_INFO &target = g_graphicsContext.GetResInfo();
144     RESOLUTION_INFO& res = *std::min_element(m_resolutions.begin(), m_resolutions.end(), closestRes(target));
145     m_currentAspect = res.strId;
146   }
147 }
148
149 CStdString CSkinInfo::GetSkinPath(const CStdString& strFile, RESOLUTION_INFO *res, const CStdString& strBaseDir /* = "" */) const
150 {
151   if (m_resolutions.empty())
152     return ""; // invalid skin
153
154   CStdString strPathToUse = Path();
155   if (!strBaseDir.empty())
156     strPathToUse = strBaseDir;
157
158   // if the caller doesn't care about the resolution just use a temporary
159   RESOLUTION_INFO tempRes;
160   if (!res)
161     res = &tempRes;
162
163   // find the closest resolution
164   const RESOLUTION_INFO &target = g_graphicsContext.GetResInfo();
165   *res = *std::min_element(m_resolutions.begin(), m_resolutions.end(), closestRes(target));
166
167   CStdString strPath = URIUtils::AddFileToFolder(strPathToUse, res->strMode);
168   strPath = URIUtils::AddFileToFolder(strPath, strFile);
169   if (CFile::Exists(strPath))
170     return strPath;
171
172   // use the default resolution
173   *res = m_defaultRes;
174
175   strPath = URIUtils::AddFileToFolder(strPathToUse, res->strMode);
176   strPath = URIUtils::AddFileToFolder(strPath, strFile);
177   return strPath;
178 }
179
180 bool CSkinInfo::HasSkinFile(const CStdString &strFile) const
181 {
182   return CFile::Exists(GetSkinPath(strFile));
183 }
184
185 double CSkinInfo::GetMinVersion()
186 {
187   return SKIN_MIN_VERSION;
188 }
189
190 void CSkinInfo::LoadIncludes()
191 {
192   CStdString includesPath = CSpecialProtocol::TranslatePathConvertCase(GetSkinPath("includes.xml"));
193   CLog::Log(LOGINFO, "Loading skin includes from %s", includesPath.c_str());
194   m_includes.ClearIncludes();
195   m_includes.LoadIncludes(includesPath);
196 }
197
198 void CSkinInfo::ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
199 {
200   if(xmlIncludeConditions)
201     xmlIncludeConditions->clear();
202
203   m_includes.ResolveIncludes(node, xmlIncludeConditions);
204 }
205
206 int CSkinInfo::GetStartWindow() const
207 {
208   int windowID = CSettings::Get().GetInt("lookandfeel.startupwindow");
209   assert(m_startupWindows.size());
210   for (vector<CStartupWindow>::const_iterator it = m_startupWindows.begin(); it != m_startupWindows.end(); it++)
211   {
212     if (windowID == (*it).m_id)
213       return windowID;
214   }
215   // return our first one
216   return m_startupWindows[0].m_id;
217 }
218
219 bool CSkinInfo::LoadStartupWindows(const cp_extension_t *ext)
220 {
221   m_startupWindows.clear();
222   m_startupWindows.push_back(CStartupWindow(WINDOW_HOME, "513"));
223   m_startupWindows.push_back(CStartupWindow(WINDOW_PVR, "19180"));
224   m_startupWindows.push_back(CStartupWindow(WINDOW_PROGRAMS, "0"));
225   m_startupWindows.push_back(CStartupWindow(WINDOW_PICTURES, "1"));
226   m_startupWindows.push_back(CStartupWindow(WINDOW_MUSIC, "2"));
227   m_startupWindows.push_back(CStartupWindow(WINDOW_VIDEOS, "3"));
228   m_startupWindows.push_back(CStartupWindow(WINDOW_FILES, "7"));
229   m_startupWindows.push_back(CStartupWindow(WINDOW_SETTINGS_MENU, "5"));
230   m_startupWindows.push_back(CStartupWindow(WINDOW_WEATHER, "8"));
231   return true;
232 }
233
234 void CSkinInfo::GetSkinPaths(std::vector<CStdString> &paths) const
235 {
236   RESOLUTION_INFO res;
237   GetSkinPath("Home.xml", &res);
238   if (!res.strMode.empty())
239     paths.push_back(URIUtils::AddFileToFolder(Path(), res.strMode));
240   if (res.strMode != m_defaultRes.strMode)
241     paths.push_back(URIUtils::AddFileToFolder(Path(), m_defaultRes.strMode));
242 }
243
244 bool CSkinInfo::TranslateResolution(const CStdString &name, RESOLUTION_INFO &res)
245 {
246   if (name.Equals("pal"))
247     res = RESOLUTION_INFO(720, 576, 4.0f/3, "pal");
248   else if (name.Equals("pal16x9"))
249     res = RESOLUTION_INFO(720, 576, 16.0f/9, "pal16x9");
250   else if (name.Equals("ntsc"))
251     res = RESOLUTION_INFO(720, 480, 4.0f/3, "ntsc");
252   else if (name.Equals("ntsc16x9"))
253     res = RESOLUTION_INFO(720, 480, 16.0f/9, "ntsc16x9");
254   else if (name.Equals("720p"))
255     res = RESOLUTION_INFO(1280, 720, 0, "720p");
256   else if (name.Equals("1080i"))
257     res = RESOLUTION_INFO(1920, 1080, 0, "1080i");
258   else
259     return false;
260   return true;
261 }
262
263 int CSkinInfo::GetFirstWindow() const
264 {
265   int startWindow = GetStartWindow();
266   if (HasSkinFile("Startup.xml"))
267     startWindow = WINDOW_STARTUP_ANIM;
268   return startWindow;
269 }
270
271 bool CSkinInfo::IsInUse() const
272 {
273   // Could extend this to prompt for reverting to the standard skin perhaps
274   return CSettings::Get().GetString("lookandfeel.skin") == ID();
275 }
276
277 const INFO::CSkinVariableString* CSkinInfo::CreateSkinVariable(const CStdString& name, int context)
278 {
279   return m_includes.CreateSkinVariable(name, context);
280 }
281
282 void CSkinInfo::SettingOptionsSkinColorsFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current)
283 {
284   CStdString settingValue = ((const CSettingString*)setting)->GetValue();
285   // Remove the .xml extension from the Themes
286   if (URIUtils::HasExtension(settingValue, ".xml"))
287     URIUtils::RemoveExtension(settingValue);
288   current = "SKINDEFAULT";
289
290   // There is a default theme (just defaults.xml)
291   // any other *.xml files are additional color themes on top of this one.
292   
293   // add the default label
294   list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard defaults.xml will be used!
295
296   // Search for colors in the Current skin!
297   vector<string> vecColors;
298   string strPath = URIUtils::AddFileToFolder(g_SkinInfo->Path(), "colors");
299
300   CFileItemList items;
301   CDirectory::GetDirectory(CSpecialProtocol::TranslatePathConvertCase(strPath), items, ".xml");
302   // Search for Themes in the Current skin!
303   for (int i = 0; i < items.Size(); ++i)
304   {
305     CFileItemPtr pItem = items[i];
306     if (!pItem->m_bIsFolder && !StringUtils::EqualsNoCase(pItem->GetLabel(), "defaults.xml"))
307     { // not the default one
308       vecColors.push_back(pItem->GetLabel().substr(0, pItem->GetLabel().size() - 4));
309     }
310   }
311   sort(vecColors.begin(), vecColors.end(), sortstringbyname());
312   for (int i = 0; i < (int) vecColors.size(); ++i)
313     list.push_back(make_pair(vecColors[i], vecColors[i]));
314
315   // try to find the best matching value
316   for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
317   {
318     if (StringUtils::EqualsNoCase(it->second, settingValue))
319       current = settingValue;
320   }
321 }
322
323 void CSkinInfo::SettingOptionsSkinFontsFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current)
324 {
325   CStdString settingValue = ((const CSettingString*)setting)->GetValue();
326   bool currentValueSet = false;
327   std::string strPath = g_SkinInfo->GetSkinPath("Font.xml");
328
329   CXBMCTinyXML xmlDoc;
330   if (!xmlDoc.LoadFile(strPath))
331   {
332     CLog::Log(LOGERROR, "FillInSkinFonts: Couldn't load %s", strPath.c_str());
333     return;
334   }
335
336   TiXmlElement* pRootElement = xmlDoc.RootElement();
337
338   std::string strValue = pRootElement->ValueStr();
339   if (strValue != "fonts")
340   {
341     CLog::Log(LOGERROR, "FillInSkinFonts: file %s doesn't start with <fonts>", strPath.c_str());
342     return;
343   }
344
345   const TiXmlNode *pChild = pRootElement->FirstChild();
346   strValue = pChild->ValueStr();
347   if (strValue == "fontset")
348   {
349     while (pChild)
350     {
351       strValue = pChild->ValueStr();
352       if (strValue == "fontset")
353       {
354         const char* idAttr = ((TiXmlElement*) pChild)->Attribute("id");
355         const char* idLocAttr = ((TiXmlElement*) pChild)->Attribute("idloc");
356         const char* unicodeAttr = ((TiXmlElement*) pChild)->Attribute("unicode");
357
358         bool isUnicode = (unicodeAttr && stricmp(unicodeAttr, "true") == 0);
359
360         bool isAllowed = true;
361         if (g_langInfo.ForceUnicodeFont() && !isUnicode)
362           isAllowed = false;
363
364         if (idAttr != NULL && isAllowed)
365         {
366           if (idLocAttr)
367             list.push_back(make_pair(g_localizeStrings.Get(atoi(idLocAttr)), idAttr));
368           else
369             list.push_back(make_pair(idAttr, idAttr));
370
371           if (StringUtils::EqualsNoCase(idAttr, settingValue.c_str()))
372             currentValueSet = true;
373         }
374       }
375
376       pChild = pChild->NextSibling();
377     }
378   }
379   else
380   {
381     // Since no fontset is defined, there is no selection of a fontset, so disable the component
382     list.push_back(make_pair(g_localizeStrings.Get(13278), ""));
383     current = "";
384     currentValueSet = true;
385   }
386
387   if (!currentValueSet)
388     current = list[0].second;
389 }
390
391 void CSkinInfo::SettingOptionsSkinSoundFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current)
392 {
393   CStdString settingValue = ((const CSettingString*)setting)->GetValue();
394   current = "SKINDEFAULT";
395
396   //find skins...
397   CFileItemList items;
398   CDirectory::GetDirectory("special://xbmc/sounds/", items);
399   CDirectory::GetDirectory("special://home/sounds/", items);
400
401   vector<string> vecSoundSkins;
402   for (int i = 0; i < items.Size(); i++)
403   {
404     CFileItemPtr pItem = items[i];
405     if (pItem->m_bIsFolder)
406     {
407       if (StringUtils::EqualsNoCase(pItem->GetLabel(), ".svn") ||
408           StringUtils::EqualsNoCase(pItem->GetLabel(), "fonts") ||
409           StringUtils::EqualsNoCase(pItem->GetLabel(), "media"))
410         continue;
411
412       vecSoundSkins.push_back(pItem->GetLabel());
413     }
414   }
415
416   list.push_back(make_pair(g_localizeStrings.Get(474), "OFF"));
417   list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT"));
418
419   sort(vecSoundSkins.begin(), vecSoundSkins.end(), sortstringbyname());
420   for (unsigned int i = 0; i < vecSoundSkins.size(); i++)
421     list.push_back(make_pair(vecSoundSkins[i], vecSoundSkins[i]));
422
423   // try to find the best matching value
424   for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
425   {
426     if (StringUtils::EqualsNoCase(it->second, settingValue))
427       current = settingValue;
428   }
429 }
430
431 void CSkinInfo::SettingOptionsSkinThemesFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current)
432 {
433   // get the choosen theme and remove the extension from the current theme (backward compat)
434   CStdString settingValue = ((const CSettingString*)setting)->GetValue();
435   URIUtils::RemoveExtension(settingValue);
436   current = "SKINDEFAULT";
437
438   // there is a default theme (just Textures.xpr/xbt)
439   // any other *.xpr|*.xbt files are additional themes on top of this one.
440
441   // add the default Label
442   list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard Textures.xpr/xbt will be used
443
444   // search for themes in the current skin!
445   vector<CStdString> vecTheme;
446   CUtil::GetSkinThemes(vecTheme);
447
448   // sort the themes for GUI and list them
449   for (int i = 0; i < (int) vecTheme.size(); ++i)
450     list.push_back(make_pair(vecTheme[i], vecTheme[i]));
451
452   // try to find the best matching value
453   for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
454   {
455     if (StringUtils::EqualsNoCase(it->second, settingValue))
456       current = settingValue;
457   }
458 }
459
460 void CSkinInfo::SettingOptionsStartupWindowsFiller(const CSetting *setting, std::vector< std::pair<std::string, int> > &list, int &current)
461 {
462   int settingValue = ((const CSettingInt *)setting)->GetValue();
463   current = -1;
464
465   const vector<CStartupWindow> &startupWindows = g_SkinInfo->GetStartupWindows();
466
467   for (vector<CStartupWindow>::const_iterator it = startupWindows.begin(); it != startupWindows.end(); it++)
468   {
469     string windowName = it->m_name;
470     if (StringUtils::IsNaturalNumber(windowName))
471       windowName = g_localizeStrings.Get(atoi(windowName.c_str()));
472     int windowID = it->m_id;
473
474     list.push_back(make_pair(windowName, windowID));
475
476     if (settingValue == windowID)
477       current = settingValue;
478   }
479
480   // if the current value hasn't been properly set, set it to the first window in the list
481   if (current < 0)
482     current = list[0].second;
483 }
484
485 } /*namespace ADDON*/