/*
* Copyright (C) 2005-2013 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, see
* .
*
*/
#include "Skin.h"
#include "AddonManager.h"
#include "LangInfo.h"
#include "Util.h"
#include "dialogs/GUIDialogKaiToast.h"
#include "dialogs/GUIDialogYesNo.h"
#include "filesystem/File.h"
#include "filesystem/SpecialProtocol.h"
#include "guilib/GUIWindowManager.h"
#include "guilib/WindowIDs.h"
#include "settings/Settings.h"
#include "settings/lib/Setting.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include "utils/StringUtils.h"
#include "ApplicationMessenger.h"
// fallback for new skin resolution code
#include "filesystem/Directory.h"
using namespace std;
using namespace XFILE;
std::shared_ptr g_SkinInfo;
namespace ADDON
{
CSkinInfo::CSkinInfo(const AddonProps &props, const RESOLUTION_INFO &resolution)
: CAddon(props), m_defaultRes(resolution), m_version(""), m_effectsSlowDown(1.f), m_debugging(false)
{
}
CSkinInfo::CSkinInfo(const cp_extension_t *ext)
: CAddon(ext), m_version(""), m_effectsSlowDown(1.f)
{
ELEMENTS elements;
if (CAddonMgr::Get().GetExtElements(ext->configuration, "res", elements))
{
for (ELEMENTS::iterator i = elements.begin(); i != elements.end(); ++i)
{
int width = atoi(CAddonMgr::Get().GetExtValue(*i, "@width").c_str());
int height = atoi(CAddonMgr::Get().GetExtValue(*i, "@height").c_str());
bool defRes = CAddonMgr::Get().GetExtValue(*i, "@default") == "true";
std::string folder = CAddonMgr::Get().GetExtValue(*i, "@folder");
float aspect = 0;
std::string strAspect = CAddonMgr::Get().GetExtValue(*i, "@aspect");
vector fracs = StringUtils::Split(strAspect, ':');
if (fracs.size() == 2)
aspect = (float)(atof(fracs[0].c_str())/atof(fracs[1].c_str()));
if (width > 0 && height > 0)
{
RESOLUTION_INFO res(width, height, aspect, folder);
res.strId = strAspect; // for skin usage, store aspect string in strId
if (defRes)
m_defaultRes = res;
m_resolutions.push_back(res);
}
}
}
else
{ // no resolutions specified -> backward compatibility
std::string defaultWide = CAddonMgr::Get().GetExtValue(ext->configuration, "@defaultwideresolution");
if (defaultWide.empty())
defaultWide = CAddonMgr::Get().GetExtValue(ext->configuration, "@defaultresolution");
TranslateResolution(defaultWide, m_defaultRes);
}
std::string str = CAddonMgr::Get().GetExtValue(ext->configuration, "@effectslowdown");
if (!str.empty())
m_effectsSlowDown = (float)atof(str.c_str());
m_debugging = CAddonMgr::Get().GetExtValue(ext->configuration, "@debugging") == "true";
LoadStartupWindows(ext);
// figure out the version
m_version = GetDependencyVersion("xbmc.gui");
}
CSkinInfo::~CSkinInfo()
{
}
AddonPtr CSkinInfo::Clone() const
{
return AddonPtr(new CSkinInfo(*this));
}
struct closestRes
{
closestRes(const RESOLUTION_INFO &target) : m_target(target) { };
bool operator()(const RESOLUTION_INFO &i, const RESOLUTION_INFO &j)
{
float diff = fabs(i.DisplayRatio() - m_target.DisplayRatio()) - fabs(j.DisplayRatio() - m_target.DisplayRatio());
if (diff < 0) return true;
if (diff > 0) return false;
diff = fabs((float)i.iHeight - m_target.iHeight) - fabs((float)j.iHeight - m_target.iHeight);
if (diff < 0) return true;
if (diff > 0) return false;
return fabs((float)i.iWidth - m_target.iWidth) < fabs((float)j.iWidth - m_target.iWidth);
}
RESOLUTION_INFO m_target;
};
void CSkinInfo::Start()
{
if (!m_resolutions.size())
{ // try falling back to whatever resolutions exist in the directory
CFileItemList items;
CDirectory::GetDirectory(Path(), items, "", DIR_FLAG_NO_FILE_DIRS);
for (int i = 0; i < items.Size(); i++)
{
RESOLUTION_INFO res;
if (items[i]->m_bIsFolder && TranslateResolution(items[i]->GetLabel(), res))
m_resolutions.push_back(res);
}
}
if (!m_resolutions.empty())
{
// find the closest resolution
const RESOLUTION_INFO &target = g_graphicsContext.GetResInfo();
RESOLUTION_INFO& res = *std::min_element(m_resolutions.begin(), m_resolutions.end(), closestRes(target));
m_currentAspect = res.strId;
}
}
std::string CSkinInfo::GetSkinPath(const std::string& strFile, RESOLUTION_INFO *res, const std::string& strBaseDir /* = "" */) const
{
if (m_resolutions.empty())
return ""; // invalid skin
std::string strPathToUse = Path();
if (!strBaseDir.empty())
strPathToUse = strBaseDir;
// if the caller doesn't care about the resolution just use a temporary
RESOLUTION_INFO tempRes;
if (!res)
res = &tempRes;
// find the closest resolution
const RESOLUTION_INFO &target = g_graphicsContext.GetResInfo();
*res = *std::min_element(m_resolutions.begin(), m_resolutions.end(), closestRes(target));
std::string strPath = URIUtils::AddFileToFolder(strPathToUse, res->strMode);
strPath = URIUtils::AddFileToFolder(strPath, strFile);
if (CFile::Exists(strPath))
return strPath;
// use the default resolution
*res = m_defaultRes;
strPath = URIUtils::AddFileToFolder(strPathToUse, res->strMode);
strPath = URIUtils::AddFileToFolder(strPath, strFile);
return strPath;
}
bool CSkinInfo::HasSkinFile(const std::string &strFile) const
{
return CFile::Exists(GetSkinPath(strFile));
}
void CSkinInfo::LoadIncludes()
{
std::string includesPath = CSpecialProtocol::TranslatePathConvertCase(GetSkinPath("includes.xml"));
CLog::Log(LOGINFO, "Loading skin includes from %s", includesPath.c_str());
m_includes.ClearIncludes();
m_includes.LoadIncludes(includesPath);
}
void CSkinInfo::ResolveIncludes(TiXmlElement *node, std::map* xmlIncludeConditions /* = NULL */)
{
if(xmlIncludeConditions)
xmlIncludeConditions->clear();
m_includes.ResolveIncludes(node, xmlIncludeConditions);
}
int CSkinInfo::GetStartWindow() const
{
int windowID = CSettings::Get().GetInt("lookandfeel.startupwindow");
assert(m_startupWindows.size());
for (vector::const_iterator it = m_startupWindows.begin(); it != m_startupWindows.end(); ++it)
{
if (windowID == (*it).m_id)
return windowID;
}
// return our first one
return m_startupWindows[0].m_id;
}
bool CSkinInfo::LoadStartupWindows(const cp_extension_t *ext)
{
m_startupWindows.clear();
m_startupWindows.push_back(CStartupWindow(WINDOW_HOME, "513"));
m_startupWindows.push_back(CStartupWindow(WINDOW_TV_CHANNELS, "19180"));
m_startupWindows.push_back(CStartupWindow(WINDOW_RADIO_CHANNELS, "19183"));
m_startupWindows.push_back(CStartupWindow(WINDOW_PROGRAMS, "0"));
m_startupWindows.push_back(CStartupWindow(WINDOW_PICTURES, "1"));
m_startupWindows.push_back(CStartupWindow(WINDOW_MUSIC, "2"));
m_startupWindows.push_back(CStartupWindow(WINDOW_VIDEOS, "3"));
m_startupWindows.push_back(CStartupWindow(WINDOW_FILES, "7"));
m_startupWindows.push_back(CStartupWindow(WINDOW_SETTINGS_MENU, "5"));
m_startupWindows.push_back(CStartupWindow(WINDOW_WEATHER, "8"));
return true;
}
void CSkinInfo::GetSkinPaths(std::vector &paths) const
{
RESOLUTION_INFO res;
GetSkinPath("Home.xml", &res);
if (!res.strMode.empty())
paths.push_back(URIUtils::AddFileToFolder(Path(), res.strMode));
if (res.strMode != m_defaultRes.strMode)
paths.push_back(URIUtils::AddFileToFolder(Path(), m_defaultRes.strMode));
}
bool CSkinInfo::TranslateResolution(const std::string &name, RESOLUTION_INFO &res)
{
std::string lower(name); StringUtils::ToLower(lower);
if (lower == "pal")
res = RESOLUTION_INFO(720, 576, 4.0f/3, "pal");
else if (lower == "pal16x9")
res = RESOLUTION_INFO(720, 576, 16.0f/9, "pal16x9");
else if (lower == "ntsc")
res = RESOLUTION_INFO(720, 480, 4.0f/3, "ntsc");
else if (lower == "ntsc16x9")
res = RESOLUTION_INFO(720, 480, 16.0f/9, "ntsc16x9");
else if (lower == "720p")
res = RESOLUTION_INFO(1280, 720, 0, "720p");
else if (lower == "1080i")
res = RESOLUTION_INFO(1920, 1080, 0, "1080i");
else
return false;
return true;
}
int CSkinInfo::GetFirstWindow() const
{
int startWindow = GetStartWindow();
if (HasSkinFile("Startup.xml"))
startWindow = WINDOW_STARTUP_ANIM;
return startWindow;
}
bool CSkinInfo::IsInUse() const
{
// Could extend this to prompt for reverting to the standard skin perhaps
return CSettings::Get().GetString("lookandfeel.skin") == ID();
}
const INFO::CSkinVariableString* CSkinInfo::CreateSkinVariable(const std::string& name, int context)
{
return m_includes.CreateSkinVariable(name, context);
}
bool CSkinInfo::OnPreInstall()
{
// check whether this is an active skin - we need to unload it if so
if (IsInUse())
{
CApplicationMessenger::Get().ExecBuiltIn("UnloadSkin", true);
return true;
}
return false;
}
void CSkinInfo::OnPostInstall(bool restart, bool update, bool modal)
{
if (restart || (!update && !modal && CGUIDialogYesNo::ShowAndGetInput(Name(), g_localizeStrings.Get(24099),"","")))
{
CGUIDialogKaiToast *toast = (CGUIDialogKaiToast *)g_windowManager.GetWindow(WINDOW_DIALOG_KAI_TOAST);
if (toast)
{
toast->ResetTimer();
toast->Close(true);
}
if (CSettings::Get().GetString("lookandfeel.skin") == ID())
CApplicationMessenger::Get().ExecBuiltIn("ReloadSkin", true);
else
CSettings::Get().SetString("lookandfeel.skin", ID());
}
}
void CSkinInfo::SettingOptionsSkinColorsFiller(const CSetting *setting, std::vector< std::pair > &list, std::string ¤t, void *data)
{
std::string settingValue = ((const CSettingString*)setting)->GetValue();
// Remove the .xml extension from the Themes
if (URIUtils::HasExtension(settingValue, ".xml"))
URIUtils::RemoveExtension(settingValue);
current = "SKINDEFAULT";
// There is a default theme (just defaults.xml)
// any other *.xml files are additional color themes on top of this one.
// add the default label
list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard defaults.xml will be used!
// Search for colors in the Current skin!
vector vecColors;
string strPath = URIUtils::AddFileToFolder(g_SkinInfo->Path(), "colors");
CFileItemList items;
CDirectory::GetDirectory(CSpecialProtocol::TranslatePathConvertCase(strPath), items, ".xml");
// Search for Themes in the Current skin!
for (int i = 0; i < items.Size(); ++i)
{
CFileItemPtr pItem = items[i];
if (!pItem->m_bIsFolder && !StringUtils::EqualsNoCase(pItem->GetLabel(), "defaults.xml"))
{ // not the default one
vecColors.push_back(pItem->GetLabel().substr(0, pItem->GetLabel().size() - 4));
}
}
sort(vecColors.begin(), vecColors.end(), sortstringbyname());
for (int i = 0; i < (int) vecColors.size(); ++i)
list.push_back(make_pair(vecColors[i], vecColors[i]));
// try to find the best matching value
for (vector< pair >::const_iterator it = list.begin(); it != list.end(); ++it)
{
if (StringUtils::EqualsNoCase(it->second, settingValue))
current = settingValue;
}
}
void CSkinInfo::SettingOptionsSkinFontsFiller(const CSetting *setting, std::vector< std::pair > &list, std::string ¤t, void *data)
{
std::string settingValue = ((const CSettingString*)setting)->GetValue();
bool currentValueSet = false;
std::string strPath = g_SkinInfo->GetSkinPath("Font.xml");
CXBMCTinyXML xmlDoc;
if (!xmlDoc.LoadFile(strPath))
{
CLog::Log(LOGERROR, "FillInSkinFonts: Couldn't load %s", strPath.c_str());
return;
}
const TiXmlElement* pRootElement = xmlDoc.RootElement();
if (!pRootElement || pRootElement->ValueStr() != "fonts")
{
CLog::Log(LOGERROR, "FillInSkinFonts: file %s doesn't start with ", strPath.c_str());
return;
}
const TiXmlElement *pChild = pRootElement->FirstChildElement("fontset");
while (pChild)
{
const char* idAttr = pChild->Attribute("id");
const char* idLocAttr = pChild->Attribute("idloc");
if (idAttr != NULL)
{
if (idLocAttr)
list.push_back(make_pair(g_localizeStrings.Get(atoi(idLocAttr)), idAttr));
else
list.push_back(make_pair(idAttr, idAttr));
if (StringUtils::EqualsNoCase(idAttr, settingValue))
currentValueSet = true;
}
pChild = pChild->NextSiblingElement("fontset");
}
if (list.empty())
{ // Since no fontset is defined, there is no selection of a fontset, so disable the component
list.push_back(make_pair(g_localizeStrings.Get(13278), ""));
current = "";
currentValueSet = true;
}
if (!currentValueSet)
current = list[0].second;
}
void CSkinInfo::SettingOptionsSkinSoundFiller(const CSetting *setting, std::vector< std::pair > &list, std::string ¤t, void *data)
{
std::string settingValue = ((const CSettingString*)setting)->GetValue();
current = "SKINDEFAULT";
//find skins...
CFileItemList items;
CDirectory::GetDirectory("special://xbmc/sounds/", items);
CDirectory::GetDirectory("special://home/sounds/", items);
vector vecSoundSkins;
for (int i = 0; i < items.Size(); i++)
{
CFileItemPtr pItem = items[i];
if (pItem->m_bIsFolder)
{
if (StringUtils::EqualsNoCase(pItem->GetLabel(), ".svn") ||
StringUtils::EqualsNoCase(pItem->GetLabel(), "fonts") ||
StringUtils::EqualsNoCase(pItem->GetLabel(), "media"))
continue;
vecSoundSkins.push_back(pItem->GetLabel());
}
}
list.push_back(make_pair(g_localizeStrings.Get(474), "OFF"));
list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT"));
sort(vecSoundSkins.begin(), vecSoundSkins.end(), sortstringbyname());
for (unsigned int i = 0; i < vecSoundSkins.size(); i++)
list.push_back(make_pair(vecSoundSkins[i], vecSoundSkins[i]));
// try to find the best matching value
for (vector< pair >::const_iterator it = list.begin(); it != list.end(); ++it)
{
if (StringUtils::EqualsNoCase(it->second, settingValue))
current = settingValue;
}
}
void CSkinInfo::SettingOptionsSkinThemesFiller(const CSetting *setting, std::vector< std::pair > &list, std::string ¤t, void *data)
{
// get the choosen theme and remove the extension from the current theme (backward compat)
std::string settingValue = ((const CSettingString*)setting)->GetValue();
URIUtils::RemoveExtension(settingValue);
current = "SKINDEFAULT";
// there is a default theme (just Textures.xpr/xbt)
// any other *.xpr|*.xbt files are additional themes on top of this one.
// add the default Label
list.push_back(make_pair(g_localizeStrings.Get(15109), "SKINDEFAULT")); // the standard Textures.xpr/xbt will be used
// search for themes in the current skin!
vector vecTheme;
CUtil::GetSkinThemes(vecTheme);
// sort the themes for GUI and list them
for (int i = 0; i < (int) vecTheme.size(); ++i)
list.push_back(make_pair(vecTheme[i], vecTheme[i]));
// try to find the best matching value
for (vector< pair >::const_iterator it = list.begin(); it != list.end(); ++it)
{
if (StringUtils::EqualsNoCase(it->second, settingValue))
current = settingValue;
}
}
void CSkinInfo::SettingOptionsStartupWindowsFiller(const CSetting *setting, std::vector< std::pair > &list, int ¤t, void *data)
{
int settingValue = ((const CSettingInt *)setting)->GetValue();
current = -1;
const vector &startupWindows = g_SkinInfo->GetStartupWindows();
for (vector::const_iterator it = startupWindows.begin(); it != startupWindows.end(); ++it)
{
string windowName = it->m_name;
if (StringUtils::IsNaturalNumber(windowName))
windowName = g_localizeStrings.Get(atoi(windowName.c_str()));
int windowID = it->m_id;
list.push_back(make_pair(windowName, windowID));
if (settingValue == windowID)
current = settingValue;
}
// if the current value hasn't been properly set, set it to the first window in the list
if (current < 0)
current = list[0].second;
}
} /*namespace ADDON*/