summaryrefslogtreecommitdiffstats
path: root/xbmc/addons/Skin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/addons/Skin.cpp')
-rw-r--r--xbmc/addons/Skin.cpp491
1 files changed, 491 insertions, 0 deletions
diff --git a/xbmc/addons/Skin.cpp b/xbmc/addons/Skin.cpp
new file mode 100644
index 0000000..acb5799
--- /dev/null
+++ b/xbmc/addons/Skin.cpp
@@ -0,0 +1,491 @@
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 "dialogs/GUIDialogKaiToast.h"
26#include "dialogs/GUIDialogYesNo.h"
27#include "filesystem/File.h"
28#include "filesystem/SpecialProtocol.h"
29#include "guilib/GUIWindowManager.h"
30#include "guilib/WindowIDs.h"
31#include "settings/Settings.h"
32#include "settings/lib/Setting.h"
33#include "utils/URIUtils.h"
34#include "utils/log.h"
35#include "utils/StringUtils.h"
36#include "ApplicationMessenger.h"
37
38// fallback for new skin resolution code
39#include "filesystem/Directory.h"
40
41using namespace std;
42using namespace XFILE;
43
44std::shared_ptr<ADDON::CSkinInfo> g_SkinInfo;
45
46namespace ADDON
47{
48
49CSkinInfo::CSkinInfo(const AddonProps &props, const RESOLUTION_INFO &resolution)
50 : CAddon(props), m_defaultRes(resolution), m_version(""), m_effectsSlowDown(1.f), m_debugging(false)
51{
52}
53
54CSkinInfo::CSkinInfo(const cp_extension_t *ext)
55 : CAddon(ext), m_version(""), m_effectsSlowDown(1.f)
56{
57 ELEMENTS elements;
58 if (CAddonMgr::Get().GetExtElements(ext->configuration, "res", elements))
59 {
60 for (ELEMENTS::iterator i = elements.begin(); i != elements.end(); ++i)
61 {
62 int width = atoi(CAddonMgr::Get().GetExtValue(*i, "@width").c_str());
63 int height = atoi(CAddonMgr::Get().GetExtValue(*i, "@height").c_str());
64 bool defRes = CAddonMgr::Get().GetExtValue(*i, "@default") == "true";
65 std::string folder = CAddonMgr::Get().GetExtValue(*i, "@folder");
66 float aspect = 0;
67 std::string strAspect = CAddonMgr::Get().GetExtValue(*i, "@aspect");
68 vector<string> fracs = StringUtils::Split(strAspect, ':');
69 if (fracs.size() == 2)
70 aspect = (float)(atof(fracs[0].c_str())/atof(fracs[1].c_str()));
71 if (width > 0 && height > 0)
72 {
73 RESOLUTION_INFO res(width, height, aspect, folder);
74 res.strId = strAspect; // for skin usage, store aspect string in strId
75 if (defRes)
76 m_defaultRes = res;
77 m_resolutions.push_back(res);
78 }
79 }
80 }
81 else
82 { // no resolutions specified -> backward compatibility
83 std::string defaultWide = CAddonMgr::Get().GetExtValue(ext->configuration, "@defaultwideresolution");
84 if (defaultWide.empty())
85 defaultWide = CAddonMgr::Get().GetExtValue(ext->configuration, "@defaultresolution");
86 TranslateResolution(defaultWide, m_defaultRes);
87 }
88
89 std::string str = CAddonMgr::Get().GetExtValue(ext->configuration, "@effectslowdown");
90 if (!str.empty())
91 m_effectsSlowDown = (float)atof(str.c_str());
92
93 m_debugging = CAddonMgr::Get().GetExtValue(ext->configuration, "@debugging") == "true";
94
95 LoadStartupWindows(ext);
96
97 // figure out the version
98 m_version = GetDependencyVersion("xbmc.gui");
99}
100
101CSkinInfo::~CSkinInfo()
102{
103}
104
105AddonPtr CSkinInfo::Clone() const
106{
107 return AddonPtr(new CSkinInfo(*this));
108}
109
110struct 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
126void 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
149std::string CSkinInfo::GetSkinPath(const std::string& strFile, RESOLUTION_INFO *res, const std::string& strBaseDir /* = "" */) const
150{
151 if (m_resolutions.empty())
152 return ""; // invalid skin
153
154 std::string 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 std::string 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
180bool CSkinInfo::HasSkinFile(const std::string &strFile) const
181{
182 return CFile::Exists(GetSkinPath(strFile));
183}
184
185void CSkinInfo::LoadIncludes()
186{
187 std::string includesPath = CSpecialProtocol::TranslatePathConvertCase(GetSkinPath("includes.xml"));
188 CLog::Log(LOGINFO, "Loading skin includes from %s", includesPath.c_str());
189 m_includes.ClearIncludes();
190 m_includes.LoadIncludes(includesPath);
191}
192
193void CSkinInfo::ResolveIncludes(TiXmlElement *node, std::map<INFO::InfoPtr, bool>* xmlIncludeConditions /* = NULL */)
194{
195 if(xmlIncludeConditions)
196 xmlIncludeConditions->clear();
197
198 m_includes.ResolveIncludes(node, xmlIncludeConditions);
199}
200
201int CSkinInfo::GetStartWindow() const
202{
203 int windowID = CSettings::Get().GetInt("lookandfeel.startupwindow");
204 assert(m_startupWindows.size());
205 for (vector<CStartupWindow>::const_iterator it = m_startupWindows.begin(); it != m_startupWindows.end(); ++it)
206 {
207 if (windowID == (*it).m_id)
208 return windowID;
209 }
210 // return our first one
211 return m_startupWindows[0].m_id;
212}
213
214bool CSkinInfo::LoadStartupWindows(const cp_extension_t *ext)
215{
216 m_startupWindows.clear();
217 m_startupWindows.push_back(CStartupWindow(WINDOW_HOME, "513"));
218 m_startupWindows.push_back(CStartupWindow(WINDOW_TV_CHANNELS, "19180"));
219 m_startupWindows.push_back(CStartupWindow(WINDOW_RADIO_CHANNELS, "19183"));
220 m_startupWindows.push_back(CStartupWindow(WINDOW_PROGRAMS, "0"));
221 m_startupWindows.push_back(CStartupWindow(WINDOW_PICTURES, "1"));
222 m_startupWindows.push_back(CStartupWindow(WINDOW_MUSIC, "2"));
223 m_startupWindows.push_back(CStartupWindow(WINDOW_VIDEOS, "3"));
224 m_startupWindows.push_back(CStartupWindow(WINDOW_FILES, "7"));
225 m_startupWindows.push_back(CStartupWindow(WINDOW_SETTINGS_MENU, "5"));
226 m_startupWindows.push_back(CStartupWindow(WINDOW_WEATHER, "8"));
227 return true;
228}
229
230void CSkinInfo::GetSkinPaths(std::vector<std::string> &paths) const
231{
232 RESOLUTION_INFO res;
233 GetSkinPath("Home.xml", &res);
234 if (!res.strMode.empty())
235 paths.push_back(URIUtils::AddFileToFolder(Path(), res.strMode));
236 if (res.strMode != m_defaultRes.strMode)
237 paths.push_back(URIUtils::AddFileToFolder(Path(), m_defaultRes.strMode));
238}
239
240bool CSkinInfo::TranslateResolution(const std::string &name, RESOLUTION_INFO &res)
241{
242 std::string lower(name); StringUtils::ToLower(lower);
243 if (lower == "pal")
244 res = RESOLUTION_INFO(720, 576, 4.0f/3, "pal");
245 else if (lower == "pal16x9")
246 res = RESOLUTION_INFO(720, 576, 16.0f/9, "pal16x9");
247 else if (lower == "ntsc")
248 res = RESOLUTION_INFO(720, 480, 4.0f/3, "ntsc");
249 else if (lower == "ntsc16x9")
250 res = RESOLUTION_INFO(720, 480, 16.0f/9, "ntsc16x9");
251 else if (lower == "720p")
252 res = RESOLUTION_INFO(1280, 720, 0, "720p");
253 else if (lower == "1080i")
254 res = RESOLUTION_INFO(1920, 1080, 0, "1080i");
255 else
256 return false;
257 return true;
258}
259
260int CSkinInfo::GetFirstWindow() const
261{
262 int startWindow = GetStartWindow();
263 if (HasSkinFile("Startup.xml"))
264 startWindow = WINDOW_STARTUP_ANIM;
265 return startWindow;
266}
267
268bool CSkinInfo::IsInUse() const
269{
270 // Could extend this to prompt for reverting to the standard skin perhaps
271 return CSettings::Get().GetString("lookandfeel.skin") == ID();
272}
273
274const INFO::CSkinVariableString* CSkinInfo::CreateSkinVariable(const std::string& name, int context)
275{
276 return m_includes.CreateSkinVariable(name, context);
277}
278
279bool CSkinInfo::OnPreInstall()
280{
281 // check whether this is an active skin - we need to unload it if so
282 if (IsInUse())
283 {
284 CApplicationMessenger::Get().ExecBuiltIn("UnloadSkin", true);
285 return true;
286 }
287 return false;
288}
289
290void CSkinInfo::OnPostInstall(bool restart, bool update, bool modal)
291{
292 if (restart || (!update && !modal && CGUIDialogYesNo::ShowAndGetInput(Name(), g_localizeStrings.Get(24099),"","")))
293 {
294 CGUIDialogKaiToast *toast = (CGUIDialogKaiToast *)g_windowManager.GetWindow(WINDOW_DIALOG_KAI_TOAST);
295 if (toast)
296 {
297 toast->ResetTimer();
298 toast->Close(true);
299 }
300 if (CSettings::Get().GetString("lookandfeel.skin") == ID())
301 CApplicationMessenger::Get().ExecBuiltIn("ReloadSkin", true);
302 else
303 CSettings::Get().SetString("lookandfeel.skin", ID());
304 }
305}
306
307void CSkinInfo::SettingOptionsSkinColorsFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current, void *data)
308{
309 std::string settingValue = ((const CSettingString*)setting)->GetValue();
310 // Remove the .xml extension from the Themes
311 if (URIUtils::HasExtension(settingValue, ".xml"))
312 URIUtils::RemoveExtension(settingValue);
313 current = "SKINDEFAULT";
314
315 // There is a default theme (just defaults.xml)
316 // any other *.xml files are additional color themes on top of this one.
317
318 // add the default label
319 list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard defaults.xml will be used!
320
321 // Search for colors in the Current skin!
322 vector<string> vecColors;
323 string strPath = URIUtils::AddFileToFolder(g_SkinInfo->Path(), "colors");
324
325 CFileItemList items;
326 CDirectory::GetDirectory(CSpecialProtocol::TranslatePathConvertCase(strPath), items, ".xml");
327 // Search for Themes in the Current skin!
328 for (int i = 0; i < items.Size(); ++i)
329 {
330 CFileItemPtr pItem = items[i];
331 if (!pItem->m_bIsFolder && !StringUtils::EqualsNoCase(pItem->GetLabel(), "defaults.xml"))
332 { // not the default one
333 vecColors.push_back(pItem->GetLabel().substr(0, pItem->GetLabel().size() - 4));
334 }
335 }
336 sort(vecColors.begin(), vecColors.end(), sortstringbyname());
337 for (int i = 0; i < (int) vecColors.size(); ++i)
338 list.push_back(make_pair(vecColors[i], vecColors[i]));
339
340 // try to find the best matching value
341 for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
342 {
343 if (StringUtils::EqualsNoCase(it->second, settingValue))
344 current = settingValue;
345 }
346}
347
348void CSkinInfo::SettingOptionsSkinFontsFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current, void *data)
349{
350 std::string settingValue = ((const CSettingString*)setting)->GetValue();
351 bool currentValueSet = false;
352 std::string strPath = g_SkinInfo->GetSkinPath("Font.xml");
353
354 CXBMCTinyXML xmlDoc;
355 if (!xmlDoc.LoadFile(strPath))
356 {
357 CLog::Log(LOGERROR, "FillInSkinFonts: Couldn't load %s", strPath.c_str());
358 return;
359 }
360
361 const TiXmlElement* pRootElement = xmlDoc.RootElement();
362 if (!pRootElement || pRootElement->ValueStr() != "fonts")
363 {
364 CLog::Log(LOGERROR, "FillInSkinFonts: file %s doesn't start with <fonts>", strPath.c_str());
365 return;
366 }
367
368 const TiXmlElement *pChild = pRootElement->FirstChildElement("fontset");
369 while (pChild)
370 {
371 const char* idAttr = pChild->Attribute("id");
372 const char* idLocAttr = pChild->Attribute("idloc");
373 if (idAttr != NULL)
374 {
375 if (idLocAttr)
376 list.push_back(make_pair(g_localizeStrings.Get(atoi(idLocAttr)), idAttr));
377 else
378 list.push_back(make_pair(idAttr, idAttr));
379
380 if (StringUtils::EqualsNoCase(idAttr, settingValue))
381 currentValueSet = true;
382 }
383 pChild = pChild->NextSiblingElement("fontset");
384 }
385
386 if (list.empty())
387 { // Since no fontset is defined, there is no selection of a fontset, so disable the component
388 list.push_back(make_pair(g_localizeStrings.Get(13278), ""));
389 current = "";
390 currentValueSet = true;
391 }
392
393 if (!currentValueSet)
394 current = list[0].second;
395}
396
397void CSkinInfo::SettingOptionsSkinSoundFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current, void *data)
398{
399 std::string settingValue = ((const CSettingString*)setting)->GetValue();
400 current = "SKINDEFAULT";
401
402 //find skins...
403 CFileItemList items;
404 CDirectory::GetDirectory("special://xbmc/sounds/", items);
405 CDirectory::GetDirectory("special://home/sounds/", items);
406
407 vector<string> vecSoundSkins;
408 for (int i = 0; i < items.Size(); i++)
409 {
410 CFileItemPtr pItem = items[i];
411 if (pItem->m_bIsFolder)
412 {
413 if (StringUtils::EqualsNoCase(pItem->GetLabel(), ".svn") ||
414 StringUtils::EqualsNoCase(pItem->GetLabel(), "fonts") ||
415 StringUtils::EqualsNoCase(pItem->GetLabel(), "media"))
416 continue;
417
418 vecSoundSkins.push_back(pItem->GetLabel());
419 }
420 }
421
422 list.push_back(make_pair(g_localizeStrings.Get(474), "OFF"));
423 list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT"));
424
425 sort(vecSoundSkins.begin(), vecSoundSkins.end(), sortstringbyname());
426 for (unsigned int i = 0; i < vecSoundSkins.size(); i++)
427 list.push_back(make_pair(vecSoundSkins[i], vecSoundSkins[i]));
428
429 // try to find the best matching value
430 for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
431 {
432 if (StringUtils::EqualsNoCase(it->second, settingValue))
433 current = settingValue;
434 }
435}
436
437void CSkinInfo::SettingOptionsSkinThemesFiller(const CSetting *setting, std::vector< std::pair<std::string, std::string> > &list, std::string &current, void *data)
438{
439 // get the choosen theme and remove the extension from the current theme (backward compat)
440 std::string settingValue = ((const CSettingString*)setting)->GetValue();
441 URIUtils::RemoveExtension(settingValue);
442 current = "SKINDEFAULT";
443
444 // there is a default theme (just Textures.xpr/xbt)
445 // any other *.xpr|*.xbt files are additional themes on top of this one.
446
447 // add the default Label
448 list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard Textures.xpr/xbt will be used
449
450 // search for themes in the current skin!
451 vector<std::string> vecTheme;
452 CUtil::GetSkinThemes(vecTheme);
453
454 // sort the themes for GUI and list them
455 for (int i = 0; i < (int) vecTheme.size(); ++i)
456 list.push_back(make_pair(vecTheme[i], vecTheme[i]));
457
458 // try to find the best matching value
459 for (vector< pair<string, string> >::const_iterator it = list.begin(); it != list.end(); ++it)
460 {
461 if (StringUtils::EqualsNoCase(it->second, settingValue))
462 current = settingValue;
463 }
464}
465
466void CSkinInfo::SettingOptionsStartupWindowsFiller(const CSetting *setting, std::vector< std::pair<std::string, int> > &list, int &current, void *data)
467{
468 int settingValue = ((const CSettingInt *)setting)->GetValue();
469 current = -1;
470
471 const vector<CStartupWindow> &startupWindows = g_SkinInfo->GetStartupWindows();
472
473 for (vector<CStartupWindow>::const_iterator it = startupWindows.begin(); it != startupWindows.end(); ++it)
474 {
475 string windowName = it->m_name;
476 if (StringUtils::IsNaturalNumber(windowName))
477 windowName = g_localizeStrings.Get(atoi(windowName.c_str()));
478 int windowID = it->m_id;
479
480 list.push_back(make_pair(windowName, windowID));
481
482 if (settingValue == windowID)
483 current = settingValue;
484 }
485
486 // if the current value hasn't been properly set, set it to the first window in the list
487 if (current < 0)
488 current = list[0].second;
489}
490
491} /*namespace ADDON*/