[videodb] remove unused seasons table from episode_view
[xbmc.git] / xbmc / application / ApplicationSkinHandling.cpp
blob5f916060ac3772f273cea6ba5e7081f67ec471ab
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "ApplicationSkinHandling.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "GUIInfoManager.h"
14 #include "GUILargeTextureManager.h"
15 #include "GUIUserMessages.h"
16 #include "PlayListPlayer.h"
17 #include "ServiceBroker.h"
18 #include "TextureCache.h"
19 #include "addons/AddonManager.h"
20 #include "addons/AddonVersion.h"
21 #include "addons/Skin.h"
22 #include "addons/addoninfo/AddonType.h"
23 #include "application/ApplicationComponents.h"
24 #include "application/ApplicationPlayer.h"
25 #include "dialogs/GUIDialogButtonMenu.h"
26 #include "dialogs/GUIDialogKaiToast.h"
27 #include "dialogs/GUIDialogSubMenu.h"
28 #include "filesystem/Directory.h"
29 #include "filesystem/DirectoryCache.h"
30 #include "guilib/GUIAudioManager.h"
31 #include "guilib/GUIColorManager.h"
32 #include "guilib/GUIComponent.h"
33 #include "guilib/GUIFontManager.h"
34 #include "guilib/GUIWindowManager.h"
35 #include "guilib/LocalizeStrings.h"
36 #include "guilib/StereoscopicsManager.h"
37 #include "messaging/ApplicationMessenger.h"
38 #include "messaging/helpers/DialogHelper.h"
39 #include "settings/Settings.h"
40 #include "settings/SettingsComponent.h"
41 #include "settings/SkinSettings.h"
42 #include "settings/lib/Setting.h"
43 #include "utils/StringUtils.h"
44 #include "utils/URIUtils.h"
45 #include "utils/XBMCTinyXML.h"
46 #include "utils/log.h"
47 #include "video/dialogs/GUIDialogFullScreenInfo.h"
49 using namespace KODI::MESSAGING;
51 CApplicationSkinHandling::CApplicationSkinHandling(IMsgTargetCallback* msgCb,
52 IWindowManagerCallback* wCb,
53 bool& bInitializing)
54 : m_msgCb(msgCb), m_wCb(wCb), m_bInitializing(bInitializing)
58 bool CApplicationSkinHandling::LoadSkin(const std::string& skinID)
60 std::shared_ptr<ADDON::CSkinInfo> skin;
62 ADDON::AddonPtr addon;
63 if (!CServiceBroker::GetAddonMgr().GetAddon(skinID, addon, ADDON::AddonType::SKIN,
64 ADDON::OnlyEnabled::CHOICE_YES))
65 return false;
66 skin = std::static_pointer_cast<ADDON::CSkinInfo>(addon);
69 // store player and rendering state
70 bool bPreviousPlayingState = false;
72 enum class RENDERING_STATE
74 NONE,
75 VIDEO,
76 GAME,
77 } previousRenderingState = RENDERING_STATE::NONE;
79 auto& components = CServiceBroker::GetAppComponents();
80 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
81 if (appPlayer && appPlayer->IsPlayingVideo())
83 bPreviousPlayingState = !appPlayer->IsPausedPlayback();
84 if (bPreviousPlayingState)
85 appPlayer->Pause();
86 appPlayer->FlushRenderer();
87 if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
89 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
90 previousRenderingState = RENDERING_STATE::VIDEO;
92 else if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() ==
93 WINDOW_FULLSCREEN_GAME)
95 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME);
96 previousRenderingState = RENDERING_STATE::GAME;
100 std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext());
102 // store current active window with its focused control
103 int currentWindowID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow();
104 int currentFocusedControlID = -1;
105 if (currentWindowID != WINDOW_INVALID)
107 CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID);
108 if (pWindow)
109 currentFocusedControlID = pWindow->GetFocusedControlID();
112 UnloadSkin();
114 skin->Start();
116 // migrate any skin-specific settings that are still stored in guisettings.xml
117 CSkinSettings::GetInstance().MigrateSettings(skin);
119 // check if the skin has been properly loaded and if it has a Home.xml
120 if (!skin->HasSkinFile("Home.xml"))
122 CLog::Log(LOGERROR, "failed to load requested skin '{}'", skin->ID());
123 return false;
126 CLog::Log(LOGINFO, " load skin from: {} (version: {})", skin->Path(),
127 skin->Version().asString());
128 g_SkinInfo = skin;
130 CLog::Log(LOGINFO, " load fonts for skin...");
131 CServiceBroker::GetWinSystem()->GetGfxContext().SetMediaDir(skin->Path());
132 g_directoryCache.ClearSubPaths(skin->Path());
134 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
135 CServiceBroker::GetGUI()->GetColorManager().Load(
136 settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS));
138 g_SkinInfo->LoadIncludes();
140 g_fontManager.LoadFonts(settings->GetString(CSettings::SETTING_LOOKANDFEEL_FONT));
142 // load in the skin strings
143 std::string langPath = URIUtils::AddFileToFolder(skin->Path(), "language");
144 URIUtils::AddSlashAtEnd(langPath);
146 g_localizeStrings.LoadSkinStrings(langPath,
147 settings->GetString(CSettings::SETTING_LOCALE_LANGUAGE));
148 g_SkinInfo->LoadTimers();
150 const auto start = std::chrono::steady_clock::now();
152 CLog::Log(LOGINFO, " load new skin...");
154 // Load custom windows
155 LoadCustomWindows();
157 const auto end = std::chrono::steady_clock::now();
158 std::chrono::duration<double, std::milli> duration = end - start;
160 CLog::Log(LOGDEBUG, "Load Skin XML: {:.2f} ms", duration.count());
162 CLog::Log(LOGINFO, " initialize new skin...");
163 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(m_msgCb);
164 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&CServiceBroker::GetPlaylistPlayer());
165 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(&g_fontManager);
166 CServiceBroker::GetGUI()->GetWindowManager().AddMsgTarget(
167 &CServiceBroker::GetGUI()->GetStereoscopicsManager());
168 CServiceBroker::GetGUI()->GetWindowManager().SetCallback(*m_wCb);
170 //@todo should be done by GUIComponents
171 CServiceBroker::GetGUI()->GetWindowManager().Initialize();
172 CServiceBroker::GetGUI()->GetAudioManager().Enable(true);
173 CServiceBroker::GetGUI()->GetAudioManager().Load();
174 CServiceBroker::GetTextureCache()->Initialize();
176 if (g_SkinInfo->HasSkinFile("DialogFullScreenInfo.xml"))
177 CServiceBroker::GetGUI()->GetWindowManager().Add(new CGUIDialogFullScreenInfo);
179 CLog::Log(LOGINFO, " skin loaded...");
181 // leave the graphics lock
182 lock.unlock();
184 // restore active window
185 if (currentWindowID != WINDOW_INVALID)
187 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(currentWindowID);
188 if (currentFocusedControlID != -1)
190 CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(currentWindowID);
191 if (pWindow && pWindow->HasSaveLastControl())
193 CGUIMessage msg(GUI_MSG_SETFOCUS, currentWindowID, currentFocusedControlID, 0);
194 pWindow->OnMessage(msg);
199 // restore player and rendering state
200 if (appPlayer && appPlayer->IsPlayingVideo())
202 if (bPreviousPlayingState)
203 appPlayer->Pause();
205 switch (previousRenderingState)
207 case RENDERING_STATE::VIDEO:
208 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_VIDEO);
209 break;
210 case RENDERING_STATE::GAME:
211 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_FULLSCREEN_GAME);
212 break;
213 default:
214 break;
218 return true;
221 void CApplicationSkinHandling::UnloadSkin()
223 if (g_SkinInfo != nullptr && m_saveSkinOnUnloading)
224 g_SkinInfo->SaveSettings();
225 else if (!m_saveSkinOnUnloading)
226 m_saveSkinOnUnloading = true;
228 if (g_SkinInfo)
229 g_SkinInfo->Unload();
231 CGUIComponent* gui = CServiceBroker::GetGUI();
232 if (gui)
234 gui->GetAudioManager().Enable(false);
236 gui->GetWindowManager().DeInitialize();
237 CServiceBroker::GetTextureCache()->Deinitialize();
239 // remove the skin-dependent window
240 gui->GetWindowManager().Delete(WINDOW_DIALOG_FULLSCREEN_INFO);
242 gui->GetTextureManager().Cleanup();
243 gui->GetLargeTextureManager().CleanupUnusedImages(true);
245 g_fontManager.Clear();
247 gui->GetColorManager().Clear();
249 gui->GetInfoManager().Clear();
252 // The g_SkinInfo shared_ptr ought to be reset here
253 // but there are too many places it's used without checking for nullptr
254 // and as a result a race condition on exit can cause a crash.
255 CLog::Log(LOGINFO, "Unloaded skin");
258 bool CApplicationSkinHandling::LoadCustomWindows()
260 // Start from wherever home.xml is
261 std::vector<std::string> vecSkinPath;
262 g_SkinInfo->GetSkinPaths(vecSkinPath);
264 for (const auto& skinPath : vecSkinPath)
266 CLog::Log(LOGINFO, "Loading custom window XMLs from skin path {}", skinPath);
268 CFileItemList items;
269 if (XFILE::CDirectory::GetDirectory(skinPath, items, ".xml", XFILE::DIR_FLAG_NO_FILE_DIRS))
271 for (const auto& item : items)
273 if (item->m_bIsFolder)
274 continue;
276 std::string skinFile = URIUtils::GetFileName(item->GetPath());
277 if (StringUtils::StartsWithNoCase(skinFile, "custom"))
279 CXBMCTinyXML xmlDoc;
280 if (!xmlDoc.LoadFile(item->GetPath()))
282 CLog::Log(LOGERROR, "Unable to load custom window XML {}. Line {}\n{}", item->GetPath(),
283 xmlDoc.ErrorRow(), xmlDoc.ErrorDesc());
284 continue;
287 // Root element should be <window>
288 TiXmlElement* pRootElement = xmlDoc.RootElement();
289 std::string strValue = pRootElement->Value();
290 if (!StringUtils::EqualsNoCase(strValue, "window"))
292 CLog::Log(LOGERROR, "No <window> root element found for custom window in {}", skinFile);
293 continue;
296 int id = WINDOW_INVALID;
298 // Read the type attribute or element to get the window type to create
299 // If no type is specified, create a CGUIWindow as default
300 std::string strType;
301 if (pRootElement->Attribute("type"))
302 strType = pRootElement->Attribute("type");
303 else
305 const TiXmlNode* pType = pRootElement->FirstChild("type");
306 if (pType && pType->FirstChild())
307 strType = pType->FirstChild()->Value();
310 // Read the id attribute or element to get the window id
311 if (!pRootElement->Attribute("id", &id))
313 const TiXmlNode* pType = pRootElement->FirstChild("id");
314 if (pType && pType->FirstChild())
315 id = atol(pType->FirstChild()->Value());
318 int windowId = id + WINDOW_HOME;
319 if (id == WINDOW_INVALID ||
320 CServiceBroker::GetGUI()->GetWindowManager().GetWindow(windowId))
322 // No id specified or id already in use
323 CLog::Log(LOGERROR, "No id specified or id already in use for custom window in {}",
324 skinFile);
325 continue;
328 CGUIWindow* pWindow = nullptr;
329 bool hasVisibleCondition = false;
331 if (StringUtils::EqualsNoCase(strType, "dialog"))
333 DialogModalityType modality = DialogModalityType::MODAL;
334 hasVisibleCondition = pRootElement->FirstChildElement("visible") != nullptr;
335 // By default dialogs that have visible conditions are considered modeless unless explicitly
336 // set to "modal" by the skinner using the "modality" attribute in the root XML element of the window
337 if (hasVisibleCondition &&
338 (!pRootElement->Attribute("modality") ||
339 !StringUtils::EqualsNoCase(pRootElement->Attribute("modality"), "modal")))
340 modality = DialogModalityType::MODELESS;
342 pWindow = new CGUIDialog(windowId, skinFile, modality);
344 else if (StringUtils::EqualsNoCase(strType, "submenu"))
346 pWindow = new CGUIDialogSubMenu(windowId, skinFile);
348 else if (StringUtils::EqualsNoCase(strType, "buttonmenu"))
350 pWindow = new CGUIDialogButtonMenu(windowId, skinFile);
352 else
354 pWindow = new CGUIWindow(windowId, skinFile);
357 if (!pWindow)
359 CLog::Log(LOGERROR, "Failed to create custom window from {}", skinFile);
360 continue;
363 pWindow->SetCustom(true);
365 // Determining whether our custom dialog is modeless (visible condition is present)
366 // will be done on load. Therefore we need to initialize the custom dialog on gui init.
367 pWindow->SetLoadType(hasVisibleCondition ? CGUIWindow::LOAD_ON_GUI_INIT
368 : CGUIWindow::KEEP_IN_MEMORY);
370 CServiceBroker::GetGUI()->GetWindowManager().AddCustomWindow(pWindow);
375 return true;
378 void CApplicationSkinHandling::ReloadSkin(bool confirm)
380 if (!g_SkinInfo || m_bInitializing)
381 return; // Don't allow reload before skin is loaded by system
383 std::string oldSkin = g_SkinInfo->ID();
385 CGUIMessage msg(GUI_MSG_LOAD_SKIN, -1,
386 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
387 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
389 const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
390 std::string newSkin = settings->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN);
391 if (LoadSkin(newSkin))
393 /* The Reset() or SetString() below will cause recursion, so the m_confirmSkinChange boolean is set so as to not prompt the
394 user as to whether they want to keep the current skin. */
395 if (confirm && m_confirmSkinChange)
397 if (HELPERS::ShowYesNoDialogText(CVariant{13123}, CVariant{13111}, CVariant{""}, CVariant{""},
398 10000) != HELPERS::DialogResponse::CHOICE_YES)
400 m_confirmSkinChange = false;
401 settings->SetString(CSettings::SETTING_LOOKANDFEEL_SKIN, oldSkin);
403 else
404 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_STARTUP_ANIM);
407 else
409 // skin failed to load - we revert to the default only if we didn't fail loading the default
410 auto setting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKIN);
411 if (!setting)
413 CLog::Log(LOGFATAL, "Failed to load setting for: {}", CSettings::SETTING_LOOKANDFEEL_SKIN);
414 return;
417 std::string defaultSkin = std::static_pointer_cast<CSettingString>(setting)->GetDefault();
418 if (newSkin != defaultSkin)
420 m_confirmSkinChange = false;
421 setting->Reset();
422 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(24102),
423 g_localizeStrings.Get(24103));
426 m_confirmSkinChange = true;
429 bool CApplicationSkinHandling::OnSettingChanged(const CSetting& setting)
431 const std::string& settingId = setting.GetId();
433 if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN ||
434 settingId == CSettings::SETTING_LOOKANDFEEL_FONT ||
435 settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME ||
436 settingId == CSettings::SETTING_LOOKANDFEEL_SKINCOLORS)
438 // check if we should ignore this change event due to changing skins in which case we have to
439 // change several settings and each one of them could lead to a complete skin reload which would
440 // result in multiple skin reloads. Therefore we manually specify to ignore specific settings
441 // which are going to be changed.
442 if (m_ignoreSkinSettingChanges)
443 return true;
445 // if the skin changes and the current color/theme/font is not the default one, reset
446 // the it to the default value
447 if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN)
449 const std::shared_ptr<CSettings> settings =
450 CServiceBroker::GetSettingsComponent()->GetSettings();
451 SettingPtr skinRelatedSetting =
452 settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINCOLORS);
453 if (!skinRelatedSetting->IsDefault())
455 m_ignoreSkinSettingChanges = true;
456 skinRelatedSetting->Reset();
459 skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_SKINTHEME);
460 if (!skinRelatedSetting->IsDefault())
462 m_ignoreSkinSettingChanges = true;
463 skinRelatedSetting->Reset();
466 skinRelatedSetting = settings->GetSetting(CSettings::SETTING_LOOKANDFEEL_FONT);
467 if (!skinRelatedSetting->IsDefault())
469 m_ignoreSkinSettingChanges = true;
470 skinRelatedSetting->Reset();
473 else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINTHEME)
475 std::shared_ptr<CSettingString> skinColorsSetting = std::static_pointer_cast<CSettingString>(
476 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
477 CSettings::SETTING_LOOKANDFEEL_SKINCOLORS));
478 std::shared_ptr<CSettingString> skinFontSetting = std::static_pointer_cast<CSettingString>(
479 CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
480 CSettings::SETTING_LOOKANDFEEL_FONT));
481 m_ignoreSkinSettingChanges = true;
483 // we also need to adjust the skin color theme and fontset
484 std::string theme = static_cast<const CSettingString&>(setting).GetValue();
485 if (setting.IsDefault() || StringUtils::EqualsNoCase(theme, "Textures.xbt"))
487 skinColorsSetting->Reset();
488 skinFontSetting->Reset();
490 else
492 URIUtils::RemoveExtension(theme);
493 skinColorsSetting->SetValue(theme);
494 skinFontSetting->SetValue(theme);
498 m_ignoreSkinSettingChanges = false;
500 if (g_SkinInfo)
502 // now we can finally reload skins
503 std::string builtin("ReloadSkin");
504 if (settingId == CSettings::SETTING_LOOKANDFEEL_SKIN && m_confirmSkinChange)
505 builtin += "(confirm)";
506 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, builtin);
509 else if (settingId == CSettings::SETTING_LOOKANDFEEL_SKINZOOM)
511 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_WINDOW_RESIZE);
512 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
514 else
515 return false;
517 return true;
520 void CApplicationSkinHandling::ProcessSkin() const
522 if (g_SkinInfo != nullptr)
523 g_SkinInfo->ProcessTimers();