2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
22 #include "AddonManager.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"
34 // fallback for new skin resolution code
35 #include "filesystem/Directory.h"
38 using namespace XFILE;
40 #define SKIN_MIN_VERSION 2.1f
42 boost::shared_ptr<ADDON::CSkinInfo> g_SkinInfo;
47 CSkinInfo::CSkinInfo(const AddonProps &props, const RESOLUTION_INFO &resolution)
48 : CAddon(props), m_defaultRes(resolution)
52 CSkinInfo::CSkinInfo(const cp_extension_t *ext)
56 if (CAddonMgr::Get().GetExtElements(ext->configuration, "res", elements))
58 for (ELEMENTS::iterator i = elements.begin(); i != elements.end(); ++i)
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");
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)
72 RESOLUTION_INFO res(width, height, aspect, folder);
73 res.strId = strAspect; // for skin usage, store aspect string in strId
76 m_resolutions.push_back(res);
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);
88 CStdString str = CAddonMgr::Get().GetExtValue(ext->configuration, "@effectslowdown");
90 m_effectsSlowDown = (float)atof(str.c_str());
92 m_effectsSlowDown = 1.f;
94 str = CAddonMgr::Get().GetExtValue(ext->configuration, "@debugging");
95 m_debugging = !strcmp(str.c_str(), "true");
97 LoadStartupWindows(ext);
101 CSkinInfo::~CSkinInfo()
105 AddonPtr CSkinInfo::Clone() const
107 return AddonPtr(new CSkinInfo(*this));
112 closestRes(const RESOLUTION_INFO &target) : m_target(target) { };
113 bool operator()(const RESOLUTION_INFO &i, const RESOLUTION_INFO &j)
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);
123 RESOLUTION_INFO m_target;
126 void CSkinInfo::Start()
128 if (!m_resolutions.size())
129 { // try falling back to whatever resolutions exist in the directory
131 CDirectory::GetDirectory(Path(), items, "", DIR_FLAG_NO_FILE_DIRS);
132 for (int i = 0; i < items.Size(); i++)
135 if (items[i]->m_bIsFolder && TranslateResolution(items[i]->GetLabel(), res))
136 m_resolutions.push_back(res);
140 if (!m_resolutions.empty())
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;
149 CStdString CSkinInfo::GetSkinPath(const CStdString& strFile, RESOLUTION_INFO *res, const CStdString& strBaseDir /* = "" */) const
151 if (m_resolutions.empty())
152 return ""; // invalid skin
154 CStdString strPathToUse = Path();
155 if (!strBaseDir.empty())
156 strPathToUse = strBaseDir;
158 // if the caller doesn't care about the resolution just use a temporary
159 RESOLUTION_INFO tempRes;
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));
167 CStdString strPath = URIUtils::AddFileToFolder(strPathToUse, res->strMode);
168 strPath = URIUtils::AddFileToFolder(strPath, strFile);
169 if (CFile::Exists(strPath))
172 // use the default resolution
175 strPath = URIUtils::AddFileToFolder(strPathToUse, res->strMode);
176 strPath = URIUtils::AddFileToFolder(strPath, strFile);
180 bool CSkinInfo::HasSkinFile(const CStdString &strFile) const
182 return CFile::Exists(GetSkinPath(strFile));
185 double CSkinInfo::GetMinVersion()
187 return SKIN_MIN_VERSION;
190 void CSkinInfo::LoadIncludes()
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);
198 void CSkinInfo::ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
200 if(xmlIncludeConditions)
201 xmlIncludeConditions->clear();
203 m_includes.ResolveIncludes(node, xmlIncludeConditions);
206 int CSkinInfo::GetStartWindow() const
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++)
212 if (windowID == (*it).m_id)
215 // return our first one
216 return m_startupWindows[0].m_id;
219 bool CSkinInfo::LoadStartupWindows(const cp_extension_t *ext)
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"));
234 void CSkinInfo::GetSkinPaths(std::vector<CStdString> &paths) const
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));
244 bool CSkinInfo::TranslateResolution(const CStdString &name, RESOLUTION_INFO &res)
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");
263 int CSkinInfo::GetFirstWindow() const
265 int startWindow = GetStartWindow();
266 if (HasSkinFile("Startup.xml"))
267 startWindow = WINDOW_STARTUP_ANIM;
271 bool CSkinInfo::IsInUse() const
273 // Could extend this to prompt for reverting to the standard skin perhaps
274 return CSettings::Get().GetString("lookandfeel.skin") == ID();
277 const INFO::CSkinVariableString* CSkinInfo::CreateSkinVariable(const CStdString& name, int context)
279 return m_includes.CreateSkinVariable(name, context);
282 void CSkinInfo::SettingOptionsSkinColorsFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string ¤t)
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";
290 // There is a default theme (just defaults.xml)
291 // any other *.xml files are additional color themes on top of this one.
293 // add the default label
294 list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard defaults.xml will be used!
296 // Search for colors in the Current skin!
297 vector<string> vecColors;
298 string strPath = URIUtils::AddFileToFolder(g_SkinInfo->Path(), "colors");
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)
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));
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]));
315 // try to find the best matching value
316 for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
318 if (StringUtils::EqualsNoCase(it->second, settingValue))
319 current = settingValue;
323 void CSkinInfo::SettingOptionsSkinFontsFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string ¤t)
325 CStdString settingValue = ((const CSettingString*)setting)->GetValue();
326 bool currentValueSet = false;
327 std::string strPath = g_SkinInfo->GetSkinPath("Font.xml");
330 if (!xmlDoc.LoadFile(strPath))
332 CLog::Log(LOGERROR, "FillInSkinFonts: Couldn't load %s", strPath.c_str());
336 TiXmlElement* pRootElement = xmlDoc.RootElement();
338 std::string strValue = pRootElement->ValueStr();
339 if (strValue != "fonts")
341 CLog::Log(LOGERROR, "FillInSkinFonts: file %s doesn't start with <fonts>", strPath.c_str());
345 const TiXmlNode *pChild = pRootElement->FirstChild();
346 strValue = pChild->ValueStr();
347 if (strValue == "fontset")
351 strValue = pChild->ValueStr();
352 if (strValue == "fontset")
354 const char* idAttr = ((TiXmlElement*) pChild)->Attribute("id");
355 const char* idLocAttr = ((TiXmlElement*) pChild)->Attribute("idloc");
356 const char* unicodeAttr = ((TiXmlElement*) pChild)->Attribute("unicode");
358 bool isUnicode = (unicodeAttr && stricmp(unicodeAttr, "true") == 0);
360 bool isAllowed = true;
361 if (g_langInfo.ForceUnicodeFont() && !isUnicode)
364 if (idAttr != NULL && isAllowed)
367 list.push_back(make_pair(g_localizeStrings.Get(atoi(idLocAttr)), idAttr));
369 list.push_back(make_pair(idAttr, idAttr));
371 if (StringUtils::EqualsNoCase(idAttr, settingValue.c_str()))
372 currentValueSet = true;
376 pChild = pChild->NextSibling();
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), ""));
384 currentValueSet = true;
387 if (!currentValueSet)
388 current = list[0].second;
391 void CSkinInfo::SettingOptionsSkinSoundFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string ¤t)
393 CStdString settingValue = ((const CSettingString*)setting)->GetValue();
394 current = "SKINDEFAULT";
398 CDirectory::GetDirectory("special://xbmc/sounds/", items);
399 CDirectory::GetDirectory("special://home/sounds/", items);
401 vector<string> vecSoundSkins;
402 for (int i = 0; i < items.Size(); i++)
404 CFileItemPtr pItem = items[i];
405 if (pItem->m_bIsFolder)
407 if (StringUtils::EqualsNoCase(pItem->GetLabel(), ".svn") ||
408 StringUtils::EqualsNoCase(pItem->GetLabel(), "fonts") ||
409 StringUtils::EqualsNoCase(pItem->GetLabel(), "media"))
412 vecSoundSkins.push_back(pItem->GetLabel());
416 list.push_back(make_pair(g_localizeStrings.Get(474), "OFF"));
417 list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT"));
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]));
423 // try to find the best matching value
424 for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
426 if (StringUtils::EqualsNoCase(it->second, settingValue))
427 current = settingValue;
431 void CSkinInfo::SettingOptionsSkinThemesFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string ¤t)
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";
438 // there is a default theme (just Textures.xpr/xbt)
439 // any other *.xpr|*.xbt files are additional themes on top of this one.
441 // add the default Label
442 list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard Textures.xpr/xbt will be used
444 // search for themes in the current skin!
445 vector<CStdString> vecTheme;
446 CUtil::GetSkinThemes(vecTheme);
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]));
452 // try to find the best matching value
453 for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
455 if (StringUtils::EqualsNoCase(it->second, settingValue))
456 current = settingValue;
460 void CSkinInfo::SettingOptionsStartupWindowsFiller(const CSetting *setting, std::vector< std::pair<std::string, int> > &list, int ¤t)
462 int settingValue = ((const CSettingInt *)setting)->GetValue();
465 const vector<CStartupWindow> &startupWindows = g_SkinInfo->GetStartupWindows();
467 for (vector<CStartupWindow>::const_iterator it = startupWindows.begin(); it != startupWindows.end(); it++)
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;
474 list.push_back(make_pair(windowName, windowID));
476 if (settingValue == windowID)
477 current = settingValue;
480 // if the current value hasn't been properly set, set it to the first window in the list
482 current = list[0].second;
485 } /*namespace ADDON*/