diff options
Diffstat (limited to 'xbmc/settings')
93 files changed, 24602 insertions, 0 deletions
diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp new file mode 100644 index 0000000..4fd93d0 --- /dev/null +++ b/xbmc/settings/AdvancedSettings.cpp @@ -0,0 +1,1559 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AdvancedSettings.h" + +#include "LangInfo.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "application/AppParams.h" +#include "filesystem/SpecialProtocol.h" +#include "network/DNSNameCache.h" +#include "profiles/ProfileManager.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingsManager.h" +#include "utils/FileUtils.h" +#include "utils/LangCodeExpander.h" +#include "utils/StringUtils.h" +#include "utils/SystemInfo.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <algorithm> +#include <climits> +#include <regex> +#include <string> +#include <vector> + +using namespace ADDON; + +CAdvancedSettings::CAdvancedSettings() +{ + m_initialized = false; + m_fullScreen = false; +} + +void CAdvancedSettings::OnSettingsLoaded() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + // load advanced settings + Load(*profileManager); + + // default players? + CLog::Log(LOGINFO, "Default Video Player: {}", m_videoDefaultPlayer); + CLog::Log(LOGINFO, "Default Audio Player: {}", m_audioDefaultPlayer); + + // setup any logging... + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings->GetBool(CSettings::SETTING_DEBUG_SHOWLOGINFO)) + { + m_logLevel = std::max(m_logLevelHint, LOG_LEVEL_DEBUG_FREEMEM); + CLog::Log(LOGINFO, "Enabled debug logging due to GUI setting ({})", m_logLevel); + } + else + { + m_logLevel = std::min(m_logLevelHint, LOG_LEVEL_DEBUG/*LOG_LEVEL_NORMAL*/); + CLog::Log(LOGINFO, "Disabled debug logging due to GUI setting. Level {}.", m_logLevel); + } + CServiceBroker::GetLogging().SetLogLevel(m_logLevel); +} + +void CAdvancedSettings::OnSettingsUnloaded() +{ + m_initialized = false; +} + +void CAdvancedSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == CSettings::SETTING_DEBUG_SHOWLOGINFO) + SetDebugMode(std::static_pointer_cast<const CSettingBool>(setting)->GetValue()); +} + +void CAdvancedSettings::Initialize(CSettingsManager& settingsMgr) +{ + Initialize(); + + const auto params = CServiceBroker::GetAppParams(); + + if (params->GetLogLevel() == LOG_LEVEL_DEBUG) + { + m_logLevel = LOG_LEVEL_DEBUG; + m_logLevelHint = LOG_LEVEL_DEBUG; + CServiceBroker::GetLogging().SetLogLevel(LOG_LEVEL_DEBUG); + } + + const std::string& settingsFile = params->GetSettingsFile(); + if (!settingsFile.empty()) + AddSettingsFile(settingsFile); + + if (params->IsStartFullScreen()) + m_startFullScreen = true; + + if (params->IsStandAlone()) + m_handleMounting = true; + + settingsMgr.RegisterSettingsHandler(this, true); + std::set<std::string> settingSet; + settingSet.insert(CSettings::SETTING_DEBUG_SHOWLOGINFO); + settingsMgr.RegisterCallback(this, settingSet); +} + +void CAdvancedSettings::Uninitialize(CSettingsManager& settingsMgr) +{ + settingsMgr.UnregisterCallback(this); + settingsMgr.UnregisterSettingsHandler(this); + settingsMgr.UnregisterSettingOptionsFiller("loggingcomponents"); + + Clear(); + + m_initialized = false; +} + +void CAdvancedSettings::Initialize() +{ + if (m_initialized) + return; + + m_audioApplyDrc = -1.0f; + m_VideoPlayerIgnoreDTSinWAV = false; + + //default hold time of 25 ms, this allows a 20 hertz sine to pass undistorted + m_limiterHold = 0.025f; + m_limiterRelease = 0.1f; + + m_seekSteps = { 10, 30, 60, 180, 300, 600, 1800 }; + + m_audioDefaultPlayer = "paplayer"; + m_audioPlayCountMinimumPercent = 90.0f; + + m_videoSubsDelayRange = 60; + m_videoAudioDelayRange = 10; + m_videoUseTimeSeeking = true; + m_videoTimeSeekForward = 30; + m_videoTimeSeekBackward = -30; + m_videoTimeSeekForwardBig = 600; + m_videoTimeSeekBackwardBig = -600; + m_videoPercentSeekForward = 2; + m_videoPercentSeekBackward = -2; + m_videoPercentSeekForwardBig = 10; + m_videoPercentSeekBackwardBig = -10; + + m_videoPPFFmpegPostProc = "ha:128:7,va,dr"; + m_videoDefaultPlayer = "VideoPlayer"; + m_videoIgnoreSecondsAtStart = 3*60; + m_videoIgnorePercentAtEnd = 8.0f; + m_videoPlayCountMinimumPercent = 90.0f; + m_videoVDPAUScaling = -1; + m_videoNonLinStretchRatio = 0.5f; + m_videoAutoScaleMaxFps = 30.0f; + m_videoCaptureUseOcclusionQuery = -1; //-1 is auto detect + m_videoVDPAUtelecine = false; + m_videoVDPAUdeintSkipChromaHD = false; + m_DXVACheckCompatibility = false; + m_DXVACheckCompatibilityPresent = false; + m_videoFpsDetect = 1; + m_maxTempo = 1.55f; + m_videoPreferStereoStream = false; + + m_videoDefaultLatency = 0.0; + + m_musicUseTimeSeeking = true; + m_musicTimeSeekForward = 10; + m_musicTimeSeekBackward = -10; + m_musicTimeSeekForwardBig = 60; + m_musicTimeSeekBackwardBig = -60; + m_musicPercentSeekForward = 1; + m_musicPercentSeekBackward = -1; + m_musicPercentSeekForwardBig = 10; + m_musicPercentSeekBackwardBig = -10; + + m_slideshowPanAmount = 2.5f; + m_slideshowZoomAmount = 5.0f; + m_slideshowBlackBarCompensation = 20.0f; + + m_songInfoDuration = 10; + + m_cddbAddress = "gnudb.gnudb.org"; + m_addSourceOnTop = false; + + m_handleMounting = CServiceBroker::GetAppParams()->IsStandAlone(); + + m_fullScreenOnMovieStart = true; + m_cachePath = "special://temp/"; + + m_videoCleanDateTimeRegExp = "(.*[^ _\\,\\.\\(\\)\\[\\]\\-])[ _\\.\\(\\)\\[\\]\\-]+(19[0-9][0-9]|20[0-9][0-9])([ _\\,\\.\\(\\)\\[\\]\\-]|[^0-9]$)?"; + + m_videoCleanStringRegExps.clear(); + m_videoCleanStringRegExps.emplace_back( + "[ " + "_\\,\\.\\(\\)\\[\\]\\-](10bit|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|3d|aac|ac3|" + "aka|atmos|avi|bd5|bdrip|bluray|brrip|cam|cd[1-9]|custom|dc|ddp|divx|divx5|dolbydigital|" + "dolbyvision|dsr|dsrip|dts|dts-hdma|dts-hra|dts-x|dv|dvd|dvd5|dvd9|dvdivx|dvdrip|dvdscr|" + "dvdscreener|extended|fragment|fs|h264|h265|hdr|hdr10|hevc|hddvd|hdrip|hdtv|hdtvrip|hrhd|" + "hrhdtv|internal|limited|multisubs|nfofix|ntsc|ogg|ogm|pal|pdtv|proper|r3|r5|read.nfo|" + "remastered|remux|repack|rerip|retail|screener|se|svcd|tc|telecine|telesync|truehd|ts|uhd|" + "unrated|ws|x264|x265|xvid|xvidvd|xxx|web-dl|webrip|www.www|\\[.*\\])([ " + "_\\,\\.\\(\\)\\[\\]\\-]|$)"); + m_videoCleanStringRegExps.emplace_back("(\\[.*\\])"); + + // this vector will be inserted at the end to + // m_moviesExcludeFromScanRegExps, m_tvshowExcludeFromScanRegExps and + // m_audioExcludeFromScanRegExps + m_allExcludeFromScanRegExps.clear(); + m_allExcludeFromScanRegExps.emplace_back("[\\/].+\\.ite[\\/]"); // ignore itunes extras dir + m_allExcludeFromScanRegExps.emplace_back("[\\/]\\.\\_"); + m_allExcludeFromScanRegExps.emplace_back("\\.DS_Store"); + m_allExcludeFromScanRegExps.emplace_back("\\.AppleDouble"); + + m_moviesExcludeFromScanRegExps.clear(); + m_moviesExcludeFromScanRegExps.emplace_back("-trailer"); + m_moviesExcludeFromScanRegExps.emplace_back("[!-._ \\\\/]sample[-._ \\\\/]"); + m_moviesExcludeFromScanRegExps.emplace_back("[\\/](proof|subs)[\\/]"); + m_moviesExcludeFromScanRegExps.insert(m_moviesExcludeFromScanRegExps.end(), + m_allExcludeFromScanRegExps.begin(), + m_allExcludeFromScanRegExps.end()); + + + m_tvshowExcludeFromScanRegExps.clear(); + m_tvshowExcludeFromScanRegExps.emplace_back("[!-._ \\\\/]sample[-._ \\\\/]"); + m_tvshowExcludeFromScanRegExps.insert(m_tvshowExcludeFromScanRegExps.end(), + m_allExcludeFromScanRegExps.begin(), + m_allExcludeFromScanRegExps.end()); + + + m_audioExcludeFromScanRegExps.clear(); + m_audioExcludeFromScanRegExps.insert(m_audioExcludeFromScanRegExps.end(), + m_allExcludeFromScanRegExps.begin(), + m_allExcludeFromScanRegExps.end()); + + m_folderStackRegExps.clear(); + m_folderStackRegExps.emplace_back("((cd|dvd|dis[ck])[0-9]+)$"); + + m_videoStackRegExps.clear(); + m_videoStackRegExps.emplace_back("(.*?)([ _.-]*(?:cd|dvd|p(?:(?:ar)?t)|dis[ck])[ _.-]*[0-9]+)(.*?)(\\.[^.]+)$"); + m_videoStackRegExps.emplace_back("(.*?)([ _.-]*(?:cd|dvd|p(?:(?:ar)?t)|dis[ck])[ _.-]*[a-d])(.*?)(\\.[^.]+)$"); + m_videoStackRegExps.emplace_back("(.*?)([ ._-]*[a-d])(.*?)(\\.[^.]+)$"); + // This one is a bit too greedy to enable by default. It will stack sequels + // in a flat dir structure, but is perfectly safe in a dir-per-vid one. + //m_videoStackRegExps.push_back("(.*?)([ ._-]*[0-9])(.*?)(\\.[^.]+)$"); + + m_tvshowEnumRegExps.clear(); + // foo.s01.e01, foo.s01_e01, S01E02 foo, S01 - E02, S01xE02 + m_tvshowEnumRegExps.push_back(TVShowRegexp(false,"s([0-9]+)[ ._x-]*e([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")); + // foo.ep01, foo.EP_01, foo.E01 + m_tvshowEnumRegExps.push_back(TVShowRegexp( + false, "[\\._ -]?()e(?:p[ ._-]?)?([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")); + // foo.yyyy.mm.dd.* (byDate=true) + m_tvshowEnumRegExps.push_back(TVShowRegexp(true,"([0-9]{4})[\\.-]([0-9]{2})[\\.-]([0-9]{2})")); + // foo.mm.dd.yyyy.* (byDate=true) + m_tvshowEnumRegExps.push_back(TVShowRegexp(true,"([0-9]{2})[\\.-]([0-9]{2})[\\.-]([0-9]{4})")); + // foo.1x09* or just /1x09* + m_tvshowEnumRegExps.push_back(TVShowRegexp(false,"[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")); + // Part I, Pt.VI, Part 1 + m_tvshowEnumRegExps.push_back(TVShowRegexp(false,"[\\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\\/]*)$")); + // This regexp is for matching special episodes by their title, e.g. foo.special.mp4 + m_tvshowEnumRegExps.push_back( + TVShowRegexp(false, "[\\\\/]([^\\\\/]+)\\.special\\.[a-z0-9]+$", 0, true)); + + // foo.103*, 103 foo + // XXX: This regex is greedy and will match years in show names. It should always be last. + m_tvshowEnumRegExps.push_back(TVShowRegexp(false,"[\\\\/\\._ -]([0-9]+)([0-9][0-9](?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([\\._ -][^\\\\/]*)$")); + + m_tvshowMultiPartEnumRegExp = "^[-_ex]+([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)"; + + m_remoteDelay = 3; + m_bScanIRServer = true; + + m_playlistAsFolders = true; + m_detectAsUdf = false; + + m_fanartRes = 1080; + m_imageRes = 720; + m_imageScalingAlgorithm = CPictureScalingAlgorithm::Default; + m_imageQualityJpeg = 4; + + m_sambaclienttimeout = 30; + m_sambadoscodepage = ""; + m_sambastatfiles = true; + + m_bHTTPDirectoryStatFilesize = false; + + m_bFTPThumbs = false; + + m_bShoutcastArt = true; + + m_musicThumbs = "folder.jpg|Folder.jpg|folder.JPG|Folder.JPG|cover.jpg|Cover.jpg|cover.jpeg|thumb.jpg|Thumb.jpg|thumb.JPG|Thumb.JPG"; + m_musicArtistExtraArt = {}; + m_musicAlbumExtraArt = {}; + + m_bMusicLibraryAllItemsOnBottom = false; + m_bMusicLibraryCleanOnUpdate = false; + m_bMusicLibraryArtistSortOnUpdate = false; + m_iMusicLibraryRecentlyAddedItems = 25; + m_strMusicLibraryAlbumFormat = ""; + m_prioritiseAPEv2tags = false; + m_musicItemSeparator = " / "; + m_musicArtistSeparators = { ";", " feat. ", " ft. " }; + m_videoItemSeparator = " / "; + m_iMusicLibraryDateAdded = 1; // prefer mtime over ctime and current time + m_bMusicLibraryUseISODates = false; + + m_bVideoLibraryAllItemsOnBottom = false; + m_iVideoLibraryRecentlyAddedItems = 25; + m_bVideoLibraryCleanOnUpdate = false; + m_bVideoLibraryUseFastHash = true; + m_bVideoScannerIgnoreErrors = false; + m_iVideoLibraryDateAdded = 1; // prefer mtime over ctime and current time + + m_videoEpisodeExtraArt = {}; + m_videoTvShowExtraArt = {}; + m_videoTvSeasonExtraArt = {}; + m_videoMovieExtraArt = {}; + m_videoMovieSetExtraArt = {}; + m_videoMusicVideoExtraArt = {}; + + m_iEpgUpdateCheckInterval = 300; /* Check every X seconds, if EPG data need to be updated. This does not mean that + every X seconds an EPG update is actually triggered, it's just the interval how + often to check whether an update should be triggered. If this value is greater + than GUI setting 'epg.epgupdate' value, then EPG updates will done with the value + specified for 'updatecheckinterval', effectively overriding the GUI setting's value. */ + m_iEpgCleanupInterval = 900; /* Remove old entries from the EPG every X seconds */ + m_iEpgActiveTagCheckInterval = 60; /* Check for updated active tags every X seconds */ + m_iEpgRetryInterruptedUpdateInterval = 30; /* Retry an interrupted EPG update after X seconds */ + m_iEpgUpdateEmptyTagsInterval = 7200; /* If a TV channel has no EPG data, try to obtain data for that channel every + X seconds. This overrides the GUI setting 'epg.epgupdate' value, but only + for channels without EPG data. If this value is less than 'updatecheckinterval' + value, then data update will be done with the interval specified by + 'updatecheckinterval'. + Example 1: epg.epgupdate = 120 (minutes!), updatecheckinterval = 300, + updateemptytagsinterval = 60 => trigger an EPG update for every + channel without EPG data every 5 minutes and trigger an EPG update + for every channel with EPG data every 2 hours. + Example 2: epg.epgupdate = 120 (minutes!), updatecheckinterval = 300, + updateemptytagsinterval = 3600 => trigger an EPG update for every + channel without EPG data every 2 hours and trigger an EPG update + for every channel with EPG data every 1 hour. */ + m_bEpgDisplayUpdatePopup = true; /* Display a progress popup while updating EPG data from clients */ + m_bEpgDisplayIncrementalUpdatePopup = false; /* Display a progress popup while doing incremental EPG updates, but + only if 'displayupdatepopup' is also enabled. */ + + m_bEdlMergeShortCommBreaks = false; // Off by default + m_EdlDisplayCommbreakNotifications = true; // On by default + m_iEdlMaxCommBreakLength = 8 * 30 + 10; // Just over 8 * 30 second commercial break. + m_iEdlMinCommBreakLength = 3 * 30; // 3 * 30 second commercial breaks. + m_iEdlMaxCommBreakGap = 4 * 30; // 4 * 30 second commercial breaks. + m_iEdlMaxStartGap = 5 * 60; // 5 minutes. + m_iEdlCommBreakAutowait = 0; // Off by default + m_iEdlCommBreakAutowind = 0; // Off by default + + m_curlconnecttimeout = 30; + m_curllowspeedtime = 20; + m_curlretries = 2; + m_curlKeepAliveInterval = 30; + m_curlDisableIPV6 = false; //Certain hardware/OS combinations have trouble + //with ipv6. + m_curlDisableHTTP2 = false; + +#if defined(TARGET_WINDOWS_DESKTOP) + m_minimizeToTray = false; +#endif +#if defined(TARGET_DARWIN_EMBEDDED) + m_startFullScreen = true; +#else + m_startFullScreen = false; +#endif + m_showExitButton = true; + m_splashImage = true; + + m_playlistRetries = 100; + m_playlistTimeout = 20; // 20 seconds timeout + m_GLRectangleHack = false; + m_iSkipLoopFilter = 0; + m_bVirtualShares = true; + + m_cpuTempCmd = ""; + m_gpuTempCmd = ""; +#if defined(TARGET_DARWIN) + // default for osx is fullscreen always on top + m_alwaysOnTop = true; +#else + // default for windows is not always on top + m_alwaysOnTop = false; +#endif + + m_iPVRTimeCorrection = 0; + m_iPVRInfoToggleInterval = 5000; + m_bPVRChannelIconsAutoScan = true; + m_bPVRAutoScanIconsUserSet = false; + m_iPVRNumericChannelSwitchTimeout = 2000; + m_iPVRTimeshiftThreshold = 10; + m_bPVRTimeshiftSimpleOSD = true; + m_PVRDefaultSortOrder.sortBy = SortByDate; + m_PVRDefaultSortOrder.sortOrder = SortOrderDescending; + + m_cacheMemSize = 1024 * 1024 * 20; // 20 MiB + m_cacheBufferMode = CACHE_BUFFER_MODE_NETWORK; // Default (buffer all network filesystems) + m_cacheChunkSize = 128 * 1024; // 128 KiB + + // the following setting determines the readRate of a player data + // as multiply of the default data read rate + m_cacheReadFactor = 4.0f; + + m_addonPackageFolderSize = 200; + + m_jsonOutputCompact = true; + m_jsonTcpPort = 9090; + + m_enableMultimediaKeys = false; + + m_canWindowed = true; + m_guiVisualizeDirtyRegions = false; + m_guiAlgorithmDirtyRegions = 3; + m_guiSmartRedraw = false; + m_airTunesPort = 36666; + m_airPlayPort = 36667; + + m_databaseMusic.Reset(); + m_databaseVideo.Reset(); + + m_useLocaleCollation = true; + + m_pictureExtensions = ".png|.jpg|.jpeg|.bmp|.gif|.ico|.tif|.tiff|.tga|.pcx|.cbz|.zip|.rss|.webp|.jp2|.apng"; + m_musicExtensions = ".nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.gdm|.imf|.m15|.sfx|.uni|.ac3|.dts|.cue|.aif|.aiff|.wpl|.xspf|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.wv|.dsp|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.wtv|.mka|.tak|.opus|.dff|.dsf|.m4b|.dtshd"; + m_videoExtensions = ".m4v|.3g2|.3gp|.nsv|.tp|.ts|.ty|.strm|.pls|.rm|.rmvb|.mpd|.m3u|.m3u8|.ifo|.mov|.qt|.divx|.xvid|.bivx|.vob|.nrg|.img|.iso|.udf|.pva|.wmv|.asf|.asx|.ogm|.m2v|.avi|.bin|.dat|.mpg|.mpeg|.mp4|.mkv|.mk3d|.avc|.vp3|.svq3|.nuv|.viv|.dv|.fli|.flv|.001|.wpl|.xspf|.zip|.vdr|.dvr-ms|.xsp|.mts|.m2t|.m2ts|.evo|.ogv|.sdp|.avs|.rec|.url|.pxml|.vc1|.h264|.rcv|.rss|.mpls|.mpl|.webm|.bdmv|.bdm|.wtv|.trp|.f4v"; + m_subtitlesExtensions = ".utf|.utf8|.utf-8|.sub|.srt|.smi|.rt|.txt|.ssa|.text|.ssa|.aqt|.jss|" + ".ass|.vtt|.idx|.ifo|.zip|.sup"; + m_discStubExtensions = ".disc"; + // internal music extensions + m_musicExtensions += "|.cdda"; + // internal video extensions + m_videoExtensions += "|.pvr"; + + m_stereoscopicregex_3d = "[-. _]3d[-. _]"; + m_stereoscopicregex_sbs = "[-. _]h?sbs[-. _]"; + m_stereoscopicregex_tab = "[-. _]h?tab[-. _]"; + + m_logLevelHint = m_logLevel = LOG_LEVEL_NORMAL; + + m_openGlDebugging = false; + + m_userAgent = g_sysinfo.GetUserAgent(); + + m_nfsTimeout = 30; + m_nfsRetries = -1; + + m_initialized = true; +} + +bool CAdvancedSettings::Load(const CProfileManager &profileManager) +{ + // NOTE: This routine should NOT set the default of any of these parameters + // it should instead use the versions of GetString/Integer/Float that + // don't take defaults in. Defaults are set in the constructor above + Initialize(); // In case of profile switch. + ParseSettingsFile("special://xbmc/system/advancedsettings.xml"); + for (unsigned int i = 0; i < m_settingsFiles.size(); i++) + ParseSettingsFile(m_settingsFiles[i]); + + ParseSettingsFile(profileManager.GetUserDataItem("advancedsettings.xml")); + + // Add the list of disc stub extensions (if any) to the list of video extensions + if (!m_discStubExtensions.empty()) + m_videoExtensions += "|" + m_discStubExtensions; + + return true; +} + +void CAdvancedSettings::ParseSettingsFile(const std::string &file) +{ + CXBMCTinyXML advancedXML; + if (!CFileUtils::Exists(file)) + { + CLog::Log(LOGINFO, "No settings file to load ({})", file); + return; + } + + if (!advancedXML.LoadFile(file)) + { + CLog::Log(LOGERROR, "Error loading {}, Line {}\n{}", file, advancedXML.ErrorRow(), + advancedXML.ErrorDesc()); + return; + } + + TiXmlElement *pRootElement = advancedXML.RootElement(); + if (!pRootElement || StringUtils::CompareNoCase(pRootElement->Value(), "advancedsettings") != 0) + { + CLog::Log(LOGERROR, "Error loading {}, no <advancedsettings> node", file); + return; + } + + // succeeded - tell the user it worked + CLog::Log(LOGINFO, "Loaded settings file from {}", file); + + //Make a copy of the AS.xml and hide advancedsettings passwords + CXBMCTinyXML advancedXMLCopy(advancedXML); + TiXmlNode *pRootElementCopy = advancedXMLCopy.RootElement(); + for (const auto& dbname : { "videodatabase", "musicdatabase", "tvdatabase", "epgdatabase" }) + { + TiXmlNode *db = pRootElementCopy->FirstChild(dbname); + if (db) + { + TiXmlNode *passTag = db->FirstChild("pass"); + if (passTag) + { + TiXmlNode *pass = passTag->FirstChild(); + if (pass) + { + passTag->RemoveChild(pass); + passTag->LinkEndChild(new TiXmlText("*****")); + } + } + } + } + TiXmlNode *network = pRootElementCopy->FirstChild("network"); + if (network) + { + TiXmlNode *passTag = network->FirstChild("httpproxypassword"); + if (passTag) + { + TiXmlNode *pass = passTag->FirstChild(); + if (pass) + { + passTag->RemoveChild(pass); + passTag->LinkEndChild(new TiXmlText("*****")); + } + } + if (network->FirstChildElement("nfstimeout")) + { +#ifdef HAS_NFS_SET_TIMEOUT + XMLUtils::GetUInt(network, "nfstimeout", m_nfsTimeout, 0, 3600); +#else + CLog::Log(LOGWARNING, "nfstimeout unsupported"); +#endif + } + if (network->FirstChildElement("nfsretries")) + { + XMLUtils::GetInt(network, "nfsretries", m_nfsRetries, -1, 30); + } + } + + // Dump contents of copied AS.xml to debug log + TiXmlPrinter printer; + printer.SetLineBreak("\n"); + printer.SetIndent(" "); + advancedXMLCopy.Accept(&printer); + // redact User/pass in URLs + std::regex redactRe("(\\w+://)\\S+:\\S+@"); + CLog::Log(LOGINFO, "Contents of {} are...\n{}", file, + std::regex_replace(printer.CStr(), redactRe, "$1USERNAME:PASSWORD@")); + + TiXmlElement *pElement = pRootElement->FirstChildElement("audio"); + if (pElement) + { + XMLUtils::GetString(pElement, "defaultplayer", m_audioDefaultPlayer); + // 101 on purpose - can be used to never automark as watched + XMLUtils::GetFloat(pElement, "playcountminimumpercent", m_audioPlayCountMinimumPercent, 0.0f, 101.0f); + + XMLUtils::GetBoolean(pElement, "usetimeseeking", m_musicUseTimeSeeking); + XMLUtils::GetInt(pElement, "timeseekforward", m_musicTimeSeekForward, 0, 6000); + XMLUtils::GetInt(pElement, "timeseekbackward", m_musicTimeSeekBackward, -6000, 0); + XMLUtils::GetInt(pElement, "timeseekforwardbig", m_musicTimeSeekForwardBig, 0, 6000); + XMLUtils::GetInt(pElement, "timeseekbackwardbig", m_musicTimeSeekBackwardBig, -6000, 0); + + XMLUtils::GetInt(pElement, "percentseekforward", m_musicPercentSeekForward, 0, 100); + XMLUtils::GetInt(pElement, "percentseekbackward", m_musicPercentSeekBackward, -100, 0); + XMLUtils::GetInt(pElement, "percentseekforwardbig", m_musicPercentSeekForwardBig, 0, 100); + XMLUtils::GetInt(pElement, "percentseekbackwardbig", m_musicPercentSeekBackwardBig, -100, 0); + + TiXmlElement* pAudioExcludes = pElement->FirstChildElement("excludefromlisting"); + if (pAudioExcludes) + GetCustomRegexps(pAudioExcludes, m_audioExcludeFromListingRegExps); + + pAudioExcludes = pElement->FirstChildElement("excludefromscan"); + if (pAudioExcludes) + GetCustomRegexps(pAudioExcludes, m_audioExcludeFromScanRegExps); + + XMLUtils::GetFloat(pElement, "applydrc", m_audioApplyDrc); + XMLUtils::GetBoolean(pElement, "VideoPlayerignoredtsinwav", m_VideoPlayerIgnoreDTSinWAV); + + XMLUtils::GetFloat(pElement, "limiterhold", m_limiterHold, 0.0f, 100.0f); + XMLUtils::GetFloat(pElement, "limiterrelease", m_limiterRelease, 0.001f, 100.0f); + XMLUtils::GetUInt(pElement, "maxpassthroughoffsyncduration", m_maxPassthroughOffSyncDuration, + 10, 100); + } + + pElement = pRootElement->FirstChildElement("x11"); + if (pElement) + { + XMLUtils::GetBoolean(pElement, "omlsync", m_omlSync); + } + + pElement = pRootElement->FirstChildElement("video"); + if (pElement) + { + XMLUtils::GetString(pElement, "stereoscopicregex3d", m_stereoscopicregex_3d); + XMLUtils::GetString(pElement, "stereoscopicregexsbs", m_stereoscopicregex_sbs); + XMLUtils::GetString(pElement, "stereoscopicregextab", m_stereoscopicregex_tab); + XMLUtils::GetFloat(pElement, "subsdelayrange", m_videoSubsDelayRange, 10, 600); + XMLUtils::GetFloat(pElement, "audiodelayrange", m_videoAudioDelayRange, 10, 600); + XMLUtils::GetString(pElement, "defaultplayer", m_videoDefaultPlayer); + XMLUtils::GetBoolean(pElement, "fullscreenonmoviestart", m_fullScreenOnMovieStart); + // 101 on purpose - can be used to never automark as watched + XMLUtils::GetFloat(pElement, "playcountminimumpercent", m_videoPlayCountMinimumPercent, 0.0f, 101.0f); + XMLUtils::GetInt(pElement, "ignoresecondsatstart", m_videoIgnoreSecondsAtStart, 0, 900); + XMLUtils::GetFloat(pElement, "ignorepercentatend", m_videoIgnorePercentAtEnd, 0, 100.0f); + + XMLUtils::GetBoolean(pElement, "usetimeseeking", m_videoUseTimeSeeking); + XMLUtils::GetInt(pElement, "timeseekforward", m_videoTimeSeekForward, 0, 6000); + XMLUtils::GetInt(pElement, "timeseekbackward", m_videoTimeSeekBackward, -6000, 0); + XMLUtils::GetInt(pElement, "timeseekforwardbig", m_videoTimeSeekForwardBig, 0, 6000); + XMLUtils::GetInt(pElement, "timeseekbackwardbig", m_videoTimeSeekBackwardBig, -6000, 0); + + XMLUtils::GetInt(pElement, "percentseekforward", m_videoPercentSeekForward, 0, 100); + XMLUtils::GetInt(pElement, "percentseekbackward", m_videoPercentSeekBackward, -100, 0); + XMLUtils::GetInt(pElement, "percentseekforwardbig", m_videoPercentSeekForwardBig, 0, 100); + XMLUtils::GetInt(pElement, "percentseekbackwardbig", m_videoPercentSeekBackwardBig, -100, 0); + + TiXmlElement* pVideoExcludes = pElement->FirstChildElement("excludefromlisting"); + if (pVideoExcludes) + GetCustomRegexps(pVideoExcludes, m_videoExcludeFromListingRegExps); + + pVideoExcludes = pElement->FirstChildElement("excludefromscan"); + if (pVideoExcludes) + GetCustomRegexps(pVideoExcludes, m_moviesExcludeFromScanRegExps); + + pVideoExcludes = pElement->FirstChildElement("excludetvshowsfromscan"); + if (pVideoExcludes) + GetCustomRegexps(pVideoExcludes, m_tvshowExcludeFromScanRegExps); + + pVideoExcludes = pElement->FirstChildElement("cleanstrings"); + if (pVideoExcludes) + GetCustomRegexps(pVideoExcludes, m_videoCleanStringRegExps); + + XMLUtils::GetString(pElement,"cleandatetime", m_videoCleanDateTimeRegExp); + XMLUtils::GetString(pElement,"ppffmpegpostprocessing",m_videoPPFFmpegPostProc); + XMLUtils::GetInt(pElement,"vdpauscaling",m_videoVDPAUScaling); + XMLUtils::GetFloat(pElement, "nonlinearstretchratio", m_videoNonLinStretchRatio, 0.01f, 1.0f); + XMLUtils::GetFloat(pElement,"autoscalemaxfps",m_videoAutoScaleMaxFps, 0.0f, 1000.0f); + XMLUtils::GetInt(pElement, "useocclusionquery", m_videoCaptureUseOcclusionQuery, -1, 1); + XMLUtils::GetBoolean(pElement,"vdpauInvTelecine",m_videoVDPAUtelecine); + XMLUtils::GetBoolean(pElement,"vdpauHDdeintSkipChroma",m_videoVDPAUdeintSkipChromaHD); + + TiXmlElement* pAdjustRefreshrate = pElement->FirstChildElement("adjustrefreshrate"); + if (pAdjustRefreshrate) + { + TiXmlElement* pRefreshOverride = pAdjustRefreshrate->FirstChildElement("override"); + while (pRefreshOverride) + { + RefreshOverride override = {}; + + float fps; + if (XMLUtils::GetFloat(pRefreshOverride, "fps", fps)) + { + override.fpsmin = fps - 0.01f; + override.fpsmax = fps + 0.01f; + } + + float fpsmin, fpsmax; + if (XMLUtils::GetFloat(pRefreshOverride, "fpsmin", fpsmin) && + XMLUtils::GetFloat(pRefreshOverride, "fpsmax", fpsmax)) + { + override.fpsmin = fpsmin; + override.fpsmax = fpsmax; + } + + float refresh; + if (XMLUtils::GetFloat(pRefreshOverride, "refresh", refresh)) + { + override.refreshmin = refresh - 0.01f; + override.refreshmax = refresh + 0.01f; + } + + float refreshmin, refreshmax; + if (XMLUtils::GetFloat(pRefreshOverride, "refreshmin", refreshmin) && + XMLUtils::GetFloat(pRefreshOverride, "refreshmax", refreshmax)) + { + override.refreshmin = refreshmin; + override.refreshmax = refreshmax; + } + + bool fpsCorrect = (override.fpsmin > 0.0f && override.fpsmax >= override.fpsmin); + bool refreshCorrect = (override.refreshmin > 0.0f && override.refreshmax >= override.refreshmin); + + if (fpsCorrect && refreshCorrect) + m_videoAdjustRefreshOverrides.push_back(override); + else + CLog::Log(LOGWARNING, + "Ignoring malformed refreshrate override, fpsmin:{:f} fpsmax:{:f} " + "refreshmin:{:f} refreshmax:{:f}", + override.fpsmin, override.fpsmax, override.refreshmin, override.refreshmax); + + pRefreshOverride = pRefreshOverride->NextSiblingElement("override"); + } + + TiXmlElement* pRefreshFallback = pAdjustRefreshrate->FirstChildElement("fallback"); + while (pRefreshFallback) + { + RefreshOverride fallback = {}; + fallback.fallback = true; + + float refresh; + if (XMLUtils::GetFloat(pRefreshFallback, "refresh", refresh)) + { + fallback.refreshmin = refresh - 0.01f; + fallback.refreshmax = refresh + 0.01f; + } + + float refreshmin, refreshmax; + if (XMLUtils::GetFloat(pRefreshFallback, "refreshmin", refreshmin) && + XMLUtils::GetFloat(pRefreshFallback, "refreshmax", refreshmax)) + { + fallback.refreshmin = refreshmin; + fallback.refreshmax = refreshmax; + } + + if (fallback.refreshmin > 0.0f && fallback.refreshmax >= fallback.refreshmin) + m_videoAdjustRefreshOverrides.push_back(fallback); + else + CLog::Log(LOGWARNING, + "Ignoring malformed refreshrate fallback, fpsmin:{:f} fpsmax:{:f} " + "refreshmin:{:f} refreshmax:{:f}", + fallback.fpsmin, fallback.fpsmax, fallback.refreshmin, fallback.refreshmax); + + pRefreshFallback = pRefreshFallback->NextSiblingElement("fallback"); + } + } + + m_DXVACheckCompatibilityPresent = XMLUtils::GetBoolean(pElement,"checkdxvacompatibility", m_DXVACheckCompatibility); + + //0 = disable fps detect, 1 = only detect on timestamps with uniform spacing, 2 detect on all timestamps + XMLUtils::GetInt(pElement, "fpsdetect", m_videoFpsDetect, 0, 2); + XMLUtils::GetFloat(pElement, "maxtempo", m_maxTempo, 1.5, 2.1); + XMLUtils::GetBoolean(pElement, "preferstereostream", m_videoPreferStereoStream); + + // Store global display latency settings + TiXmlElement* pVideoLatency = pElement->FirstChildElement("latency"); + if (pVideoLatency) + { + float refresh, refreshmin, refreshmax, delay; + TiXmlElement* pRefreshVideoLatency = pVideoLatency->FirstChildElement("refresh"); + + while (pRefreshVideoLatency) + { + RefreshVideoLatency videolatency = {}; + + if (XMLUtils::GetFloat(pRefreshVideoLatency, "rate", refresh)) + { + videolatency.refreshmin = refresh - 0.01f; + videolatency.refreshmax = refresh + 0.01f; + } + else if (XMLUtils::GetFloat(pRefreshVideoLatency, "min", refreshmin) && + XMLUtils::GetFloat(pRefreshVideoLatency, "max", refreshmax)) + { + videolatency.refreshmin = refreshmin; + videolatency.refreshmax = refreshmax; + } + if (XMLUtils::GetFloat(pRefreshVideoLatency, "delay", delay, -600.0f, 600.0f)) + videolatency.delay = delay; + + if (videolatency.refreshmin > 0.0f && videolatency.refreshmax >= videolatency.refreshmin) + m_videoRefreshLatency.push_back(videolatency); + else + CLog::Log(LOGWARNING, + "Ignoring malformed display latency <refresh> entry, min:{:f} max:{:f}", + videolatency.refreshmin, videolatency.refreshmax); + + pRefreshVideoLatency = pRefreshVideoLatency->NextSiblingElement("refresh"); + } + + // Get default global display latency + XMLUtils::GetFloat(pVideoLatency, "delay", m_videoDefaultLatency, -600.0f, 600.0f); + } + } + + pElement = pRootElement->FirstChildElement("musiclibrary"); + if (pElement) + { + XMLUtils::GetInt(pElement, "recentlyaddeditems", m_iMusicLibraryRecentlyAddedItems, 1, INT_MAX); + XMLUtils::GetBoolean(pElement, "prioritiseapetags", m_prioritiseAPEv2tags); + XMLUtils::GetBoolean(pElement, "allitemsonbottom", m_bMusicLibraryAllItemsOnBottom); + XMLUtils::GetBoolean(pElement, "cleanonupdate", m_bMusicLibraryCleanOnUpdate); + XMLUtils::GetBoolean(pElement, "artistsortonupdate", m_bMusicLibraryArtistSortOnUpdate); + XMLUtils::GetString(pElement, "albumformat", m_strMusicLibraryAlbumFormat); + XMLUtils::GetString(pElement, "itemseparator", m_musicItemSeparator); + XMLUtils::GetInt(pElement, "dateadded", m_iMusicLibraryDateAdded); + XMLUtils::GetBoolean(pElement, "useisodates", m_bMusicLibraryUseISODates); + //Music artist name separators + TiXmlElement* separators = pElement->FirstChildElement("artistseparators"); + if (separators) + { + m_musicArtistSeparators.clear(); + TiXmlNode* separator = separators->FirstChild("separator"); + while (separator) + { + if (separator->FirstChild()) + m_musicArtistSeparators.push_back(separator->FirstChild()->ValueStr()); + separator = separator->NextSibling("separator"); + } + } + + SetExtraArtwork(pElement->FirstChildElement("artistextraart"), m_musicArtistExtraArt); + SetExtraArtwork(pElement->FirstChildElement("albumextraart"), m_musicAlbumExtraArt); + } + + pElement = pRootElement->FirstChildElement("videolibrary"); + if (pElement) + { + XMLUtils::GetBoolean(pElement, "allitemsonbottom", m_bVideoLibraryAllItemsOnBottom); + XMLUtils::GetInt(pElement, "recentlyaddeditems", m_iVideoLibraryRecentlyAddedItems, 1, INT_MAX); + XMLUtils::GetBoolean(pElement, "cleanonupdate", m_bVideoLibraryCleanOnUpdate); + XMLUtils::GetBoolean(pElement, "usefasthash", m_bVideoLibraryUseFastHash); + XMLUtils::GetString(pElement, "itemseparator", m_videoItemSeparator); + XMLUtils::GetBoolean(pElement, "importwatchedstate", m_bVideoLibraryImportWatchedState); + XMLUtils::GetBoolean(pElement, "importresumepoint", m_bVideoLibraryImportResumePoint); + XMLUtils::GetInt(pElement, "dateadded", m_iVideoLibraryDateAdded); + + SetExtraArtwork(pElement->FirstChildElement("episodeextraart"), m_videoEpisodeExtraArt); + SetExtraArtwork(pElement->FirstChildElement("tvshowextraart"), m_videoTvShowExtraArt); + SetExtraArtwork(pElement->FirstChildElement("tvseasonextraart"), m_videoTvSeasonExtraArt); + SetExtraArtwork(pElement->FirstChildElement("movieextraart"), m_videoMovieExtraArt); + SetExtraArtwork(pElement->FirstChildElement("moviesetextraart"), m_videoMovieSetExtraArt); + SetExtraArtwork(pElement->FirstChildElement("musicvideoextraart"), m_videoMusicVideoExtraArt); + } + + pElement = pRootElement->FirstChildElement("videoscanner"); + if (pElement) + { + XMLUtils::GetBoolean(pElement, "ignoreerrors", m_bVideoScannerIgnoreErrors); + } + + // Backward-compatibility of ExternalPlayer config + pElement = pRootElement->FirstChildElement("externalplayer"); + if (pElement) + { + CLog::Log(LOGWARNING, "External player configuration has been removed from advancedsettings.xml. It can now be configured in userdata/playercorefactory.xml"); + } + pElement = pRootElement->FirstChildElement("slideshow"); + if (pElement) + { + XMLUtils::GetFloat(pElement, "panamount", m_slideshowPanAmount, 0.0f, 20.0f); + XMLUtils::GetFloat(pElement, "zoomamount", m_slideshowZoomAmount, 0.0f, 20.0f); + XMLUtils::GetFloat(pElement, "blackbarcompensation", m_slideshowBlackBarCompensation, 0.0f, 50.0f); + } + + pElement = pRootElement->FirstChildElement("network"); + if (pElement) + { + XMLUtils::GetInt(pElement, "curlclienttimeout", m_curlconnecttimeout, 1, 1000); + XMLUtils::GetInt(pElement, "curllowspeedtime", m_curllowspeedtime, 1, 1000); + XMLUtils::GetInt(pElement, "curlretries", m_curlretries, 0, 10); + XMLUtils::GetInt(pElement, "curlkeepaliveinterval", m_curlKeepAliveInterval, 0, 300); + XMLUtils::GetBoolean(pElement, "disableipv6", m_curlDisableIPV6); + XMLUtils::GetBoolean(pElement, "disablehttp2", m_curlDisableHTTP2); + XMLUtils::GetString(pElement, "catrustfile", m_caTrustFile); + } + + pElement = pRootElement->FirstChildElement("cache"); + if (pElement) + { + XMLUtils::GetUInt(pElement, "memorysize", m_cacheMemSize); + XMLUtils::GetUInt(pElement, "buffermode", m_cacheBufferMode, 0, 4); + XMLUtils::GetUInt(pElement, "chunksize", m_cacheChunkSize, 256, 1024 * 1024); + XMLUtils::GetFloat(pElement, "readfactor", m_cacheReadFactor); + } + + pElement = pRootElement->FirstChildElement("jsonrpc"); + if (pElement) + { + XMLUtils::GetBoolean(pElement, "compactoutput", m_jsonOutputCompact); + XMLUtils::GetUInt(pElement, "tcpport", m_jsonTcpPort); + } + + pElement = pRootElement->FirstChildElement("samba"); + if (pElement) + { + XMLUtils::GetString(pElement, "doscodepage", m_sambadoscodepage); + XMLUtils::GetInt(pElement, "clienttimeout", m_sambaclienttimeout, 5, 100); + XMLUtils::GetBoolean(pElement, "statfiles", m_sambastatfiles); + } + + pElement = pRootElement->FirstChildElement("httpdirectory"); + if (pElement) + XMLUtils::GetBoolean(pElement, "statfilesize", m_bHTTPDirectoryStatFilesize); + + pElement = pRootElement->FirstChildElement("ftp"); + if (pElement) + { + XMLUtils::GetBoolean(pElement, "remotethumbs", m_bFTPThumbs); + } + + pElement = pRootElement->FirstChildElement("loglevel"); + if (pElement) + { // read the loglevel setting, so set the setting advanced to hide it in GUI + // as altering it will do nothing - we don't write to advancedsettings.xml + XMLUtils::GetInt(pRootElement, "loglevel", m_logLevelHint, LOG_LEVEL_NONE, LOG_LEVEL_MAX); + const char* hide = pElement->Attribute("hide"); + if (hide == NULL || StringUtils::CompareNoCase("false", hide, 5) != 0) + { + SettingPtr setting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(CSettings::SETTING_DEBUG_SHOWLOGINFO); + if (setting != NULL) + setting->SetVisible(false); + } + m_logLevel = std::max(m_logLevel, m_logLevelHint); + CServiceBroker::GetLogging().SetLogLevel(m_logLevel); + } + + XMLUtils::GetString(pRootElement, "cddbaddress", m_cddbAddress); + XMLUtils::GetBoolean(pRootElement, "addsourceontop", m_addSourceOnTop); + + //airtunes + airplay + XMLUtils::GetInt(pRootElement, "airtunesport", m_airTunesPort); + XMLUtils::GetInt(pRootElement, "airplayport", m_airPlayPort); + + XMLUtils::GetBoolean(pRootElement, "handlemounting", m_handleMounting); + XMLUtils::GetBoolean(pRootElement, "automountopticalmedia", m_autoMountOpticalMedia); + +#if defined(TARGET_WINDOWS_DESKTOP) + XMLUtils::GetBoolean(pRootElement, "minimizetotray", m_minimizeToTray); +#endif +#if defined(TARGET_DARWIN_OSX) || defined(TARGET_WINDOWS) + XMLUtils::GetBoolean(pRootElement, "fullscreen", m_startFullScreen); +#endif + XMLUtils::GetBoolean(pRootElement, "splash", m_splashImage); + XMLUtils::GetBoolean(pRootElement, "showexitbutton", m_showExitButton); + XMLUtils::GetBoolean(pRootElement, "canwindowed", m_canWindowed); + + XMLUtils::GetInt(pRootElement, "songinfoduration", m_songInfoDuration, 0, INT_MAX); + XMLUtils::GetInt(pRootElement, "playlistretries", m_playlistRetries, -1, 5000); + XMLUtils::GetInt(pRootElement, "playlisttimeout", m_playlistTimeout, 0, 5000); + + XMLUtils::GetBoolean(pRootElement,"glrectanglehack", m_GLRectangleHack); + XMLUtils::GetInt(pRootElement,"skiploopfilter", m_iSkipLoopFilter, -16, 48); + + XMLUtils::GetBoolean(pRootElement,"virtualshares", m_bVirtualShares); + XMLUtils::GetUInt(pRootElement, "packagefoldersize", m_addonPackageFolderSize); + + // EPG + pElement = pRootElement->FirstChildElement("epg"); + if (pElement) + { + XMLUtils::GetInt(pElement, "updatecheckinterval", m_iEpgUpdateCheckInterval); + XMLUtils::GetInt(pElement, "cleanupinterval", m_iEpgCleanupInterval); + XMLUtils::GetInt(pElement, "activetagcheckinterval", m_iEpgActiveTagCheckInterval); + XMLUtils::GetInt(pElement, "retryinterruptedupdateinterval", m_iEpgRetryInterruptedUpdateInterval); + XMLUtils::GetInt(pElement, "updateemptytagsinterval", m_iEpgUpdateEmptyTagsInterval); + XMLUtils::GetBoolean(pElement, "displayupdatepopup", m_bEpgDisplayUpdatePopup); + XMLUtils::GetBoolean(pElement, "displayincrementalupdatepopup", m_bEpgDisplayIncrementalUpdatePopup); + } + + // EDL commercial break handling + pElement = pRootElement->FirstChildElement("edl"); + if (pElement) + { + XMLUtils::GetBoolean(pElement, "mergeshortcommbreaks", m_bEdlMergeShortCommBreaks); + XMLUtils::GetBoolean(pElement, "displaycommbreaknotifications", + m_EdlDisplayCommbreakNotifications); + XMLUtils::GetInt(pElement, "maxcommbreaklength", m_iEdlMaxCommBreakLength, 0, 10 * 60); // Between 0 and 10 minutes + XMLUtils::GetInt(pElement, "mincommbreaklength", m_iEdlMinCommBreakLength, 0, 5 * 60); // Between 0 and 5 minutes + XMLUtils::GetInt(pElement, "maxcommbreakgap", m_iEdlMaxCommBreakGap, 0, 5 * 60); // Between 0 and 5 minutes. + XMLUtils::GetInt(pElement, "maxstartgap", m_iEdlMaxStartGap, 0, 10 * 60); // Between 0 and 10 minutes + XMLUtils::GetInt(pElement, "commbreakautowait", m_iEdlCommBreakAutowait, -60, 60); // Between -60 and 60 seconds + XMLUtils::GetInt(pElement, "commbreakautowind", m_iEdlCommBreakAutowind, -60, 60); // Between -60 and 60 seconds + } + + // picture exclude regexps + TiXmlElement* pPictureExcludes = pRootElement->FirstChildElement("pictureexcludes"); + if (pPictureExcludes) + GetCustomRegexps(pPictureExcludes, m_pictureExcludeFromListingRegExps); + + // picture extensions + TiXmlElement* pExts = pRootElement->FirstChildElement("pictureextensions"); + if (pExts) + GetCustomExtensions(pExts, m_pictureExtensions); + + // music extensions + pExts = pRootElement->FirstChildElement("musicextensions"); + if (pExts) + GetCustomExtensions(pExts, m_musicExtensions); + + // video extensions + pExts = pRootElement->FirstChildElement("videoextensions"); + if (pExts) + GetCustomExtensions(pExts, m_videoExtensions); + + // stub extensions + pExts = pRootElement->FirstChildElement("discstubextensions"); + if (pExts) + GetCustomExtensions(pExts, m_discStubExtensions); + + m_vecTokens.clear(); + CLangInfo::LoadTokens(pRootElement->FirstChild("sorttokens"),m_vecTokens); + + //! @todo Should cache path be given in terms of our predefined paths?? + //! Are we even going to have predefined paths?? + std::string tmp; + if (XMLUtils::GetPath(pRootElement, "cachepath", tmp)) + m_cachePath = tmp; + URIUtils::AddSlashAtEnd(m_cachePath); + + g_LangCodeExpander.LoadUserCodes(pRootElement->FirstChildElement("languagecodes")); + + // trailer matching regexps + TiXmlElement* pTrailerMatching = pRootElement->FirstChildElement("trailermatching"); + if (pTrailerMatching) + GetCustomRegexps(pTrailerMatching, m_trailerMatchRegExps); + + //everything that's a trailer is not a movie + m_moviesExcludeFromScanRegExps.insert(m_moviesExcludeFromScanRegExps.end(), + m_trailerMatchRegExps.begin(), + m_trailerMatchRegExps.end()); + + // video stacking regexps + TiXmlElement* pVideoStacking = pRootElement->FirstChildElement("moviestacking"); + if (pVideoStacking) + GetCustomRegexps(pVideoStacking, m_videoStackRegExps); + + // folder stacking regexps + TiXmlElement* pFolderStacking = pRootElement->FirstChildElement("folderstacking"); + if (pFolderStacking) + GetCustomRegexps(pFolderStacking, m_folderStackRegExps); + + //tv stacking regexps + TiXmlElement* pTVStacking = pRootElement->FirstChildElement("tvshowmatching"); + if (pTVStacking) + GetCustomTVRegexps(pTVStacking, m_tvshowEnumRegExps); + + //tv multipart enumeration regexp + XMLUtils::GetString(pRootElement, "tvmultipartmatching", m_tvshowMultiPartEnumRegExp); + + // path substitutions + TiXmlElement* pPathSubstitution = pRootElement->FirstChildElement("pathsubstitution"); + if (pPathSubstitution) + { + m_pathSubstitutions.clear(); + CLog::Log(LOGDEBUG,"Configuring path substitutions"); + TiXmlNode* pSubstitute = pPathSubstitution->FirstChildElement("substitute"); + while (pSubstitute) + { + std::string strFrom, strTo; + TiXmlNode* pFrom = pSubstitute->FirstChild("from"); + if (pFrom && !pFrom->NoChildren()) + strFrom = CSpecialProtocol::TranslatePath(pFrom->FirstChild()->Value()).c_str(); + TiXmlNode* pTo = pSubstitute->FirstChild("to"); + if (pTo && !pTo->NoChildren()) + strTo = pTo->FirstChild()->Value(); + + if (!strFrom.empty() && !strTo.empty()) + { + CLog::Log(LOGDEBUG," Registering substitution pair:"); + CLog::Log(LOGDEBUG, " From: [{}]", CURL::GetRedacted(strFrom)); + CLog::Log(LOGDEBUG, " To: [{}]", CURL::GetRedacted(strTo)); + m_pathSubstitutions.push_back(std::make_pair(strFrom,strTo)); + } + else + { + // error message about missing tag + if (strFrom.empty()) + CLog::Log(LOGERROR," Missing <from> tag"); + else + CLog::Log(LOGERROR," Missing <to> tag"); + } + + // get next one + pSubstitute = pSubstitute->NextSiblingElement("substitute"); + } + } + + XMLUtils::GetInt(pRootElement, "remotedelay", m_remoteDelay, 0, 20); + XMLUtils::GetBoolean(pRootElement, "scanirserver", m_bScanIRServer); + + XMLUtils::GetUInt(pRootElement, "fanartres", m_fanartRes, 0, 9999); + XMLUtils::GetUInt(pRootElement, "imageres", m_imageRes, 0, 9999); + if (XMLUtils::GetString(pRootElement, "imagescalingalgorithm", tmp)) + m_imageScalingAlgorithm = CPictureScalingAlgorithm::FromString(tmp); + XMLUtils::GetUInt(pRootElement, "imagequalityjpeg", m_imageQualityJpeg, 0, 21); + XMLUtils::GetBoolean(pRootElement, "playlistasfolders", m_playlistAsFolders); + XMLUtils::GetBoolean(pRootElement, "uselocalecollation", m_useLocaleCollation); + XMLUtils::GetBoolean(pRootElement, "detectasudf", m_detectAsUdf); + + // music thumbs + TiXmlElement* pThumbs = pRootElement->FirstChildElement("musicthumbs"); + if (pThumbs) + GetCustomExtensions(pThumbs,m_musicThumbs); + + // show art for shoutcast v2 streams (set to false for devices with limited storage) + XMLUtils::GetBoolean(pRootElement, "shoutcastart", m_bShoutcastArt); + // music filename->tag filters + TiXmlElement* filters = pRootElement->FirstChildElement("musicfilenamefilters"); + if (filters) + { + TiXmlNode* filter = filters->FirstChild("filter"); + while (filter) + { + if (filter->FirstChild()) + m_musicTagsFromFileFilters.push_back(filter->FirstChild()->ValueStr()); + filter = filter->NextSibling("filter"); + } + } + + TiXmlElement* pHostEntries = pRootElement->FirstChildElement("hosts"); + if (pHostEntries) + { + TiXmlElement* element = pHostEntries->FirstChildElement("entry"); + while(element) + { + if(!element->NoChildren()) + { + std::string name = XMLUtils::GetAttribute(element, "name"); + std::string value = element->FirstChild()->ValueStr(); + if (!name.empty()) + CDNSNameCache::Add(name, value); + } + element = element->NextSiblingElement("entry"); + } + } + + XMLUtils::GetString(pRootElement, "cputempcommand", m_cpuTempCmd); + XMLUtils::GetString(pRootElement, "gputempcommand", m_gpuTempCmd); + + XMLUtils::GetBoolean(pRootElement, "alwaysontop", m_alwaysOnTop); + + TiXmlElement *pPVR = pRootElement->FirstChildElement("pvr"); + if (pPVR) + { + XMLUtils::GetInt(pPVR, "timecorrection", m_iPVRTimeCorrection, 0, 1440); + XMLUtils::GetInt(pPVR, "infotoggleinterval", m_iPVRInfoToggleInterval, 0, 30000); + XMLUtils::GetBoolean(pPVR, "channeliconsautoscan", m_bPVRChannelIconsAutoScan); + XMLUtils::GetBoolean(pPVR, "autoscaniconsuserset", m_bPVRAutoScanIconsUserSet); + XMLUtils::GetInt(pPVR, "numericchannelswitchtimeout", m_iPVRNumericChannelSwitchTimeout, 50, 60000); + XMLUtils::GetInt(pPVR, "timeshiftthreshold", m_iPVRTimeshiftThreshold, 0, 60); + XMLUtils::GetBoolean(pPVR, "timeshiftsimpleosd", m_bPVRTimeshiftSimpleOSD); + TiXmlElement* pSortDecription = pPVR->FirstChildElement("pvrrecordings"); + if (pSortDecription) + { + const char* XML_SORTMETHOD = "sortmethod"; + const char* XML_SORTORDER = "sortorder"; + int sortMethod; + // ignore SortByTime for duration defaults + if (XMLUtils::GetInt(pSortDecription, XML_SORTMETHOD, sortMethod, SortByLabel, SortByFile)) + { + int sortOrder; + if (XMLUtils::GetInt(pSortDecription, XML_SORTORDER, sortOrder, SortOrderAscending, + SortOrderDescending)) + { + m_PVRDefaultSortOrder.sortBy = (SortBy)sortMethod; + m_PVRDefaultSortOrder.sortOrder = (SortOrder)sortOrder; + } + } + } + } + + TiXmlElement* pDatabase = pRootElement->FirstChildElement("videodatabase"); + if (pDatabase) + { + CLog::Log(LOGWARNING, "VIDEO database configuration is experimental."); + XMLUtils::GetString(pDatabase, "type", m_databaseVideo.type); + XMLUtils::GetString(pDatabase, "host", m_databaseVideo.host); + XMLUtils::GetString(pDatabase, "port", m_databaseVideo.port); + XMLUtils::GetString(pDatabase, "user", m_databaseVideo.user); + XMLUtils::GetString(pDatabase, "pass", m_databaseVideo.pass); + XMLUtils::GetString(pDatabase, "name", m_databaseVideo.name); + XMLUtils::GetString(pDatabase, "key", m_databaseVideo.key); + XMLUtils::GetString(pDatabase, "cert", m_databaseVideo.cert); + XMLUtils::GetString(pDatabase, "ca", m_databaseVideo.ca); + XMLUtils::GetString(pDatabase, "capath", m_databaseVideo.capath); + XMLUtils::GetString(pDatabase, "ciphers", m_databaseVideo.ciphers); + XMLUtils::GetBoolean(pDatabase, "compression", m_databaseVideo.compression); + } + + pDatabase = pRootElement->FirstChildElement("musicdatabase"); + if (pDatabase) + { + XMLUtils::GetString(pDatabase, "type", m_databaseMusic.type); + XMLUtils::GetString(pDatabase, "host", m_databaseMusic.host); + XMLUtils::GetString(pDatabase, "port", m_databaseMusic.port); + XMLUtils::GetString(pDatabase, "user", m_databaseMusic.user); + XMLUtils::GetString(pDatabase, "pass", m_databaseMusic.pass); + XMLUtils::GetString(pDatabase, "name", m_databaseMusic.name); + XMLUtils::GetString(pDatabase, "key", m_databaseMusic.key); + XMLUtils::GetString(pDatabase, "cert", m_databaseMusic.cert); + XMLUtils::GetString(pDatabase, "ca", m_databaseMusic.ca); + XMLUtils::GetString(pDatabase, "capath", m_databaseMusic.capath); + XMLUtils::GetString(pDatabase, "ciphers", m_databaseMusic.ciphers); + XMLUtils::GetBoolean(pDatabase, "compression", m_databaseMusic.compression); + } + + pDatabase = pRootElement->FirstChildElement("tvdatabase"); + if (pDatabase) + { + XMLUtils::GetString(pDatabase, "type", m_databaseTV.type); + XMLUtils::GetString(pDatabase, "host", m_databaseTV.host); + XMLUtils::GetString(pDatabase, "port", m_databaseTV.port); + XMLUtils::GetString(pDatabase, "user", m_databaseTV.user); + XMLUtils::GetString(pDatabase, "pass", m_databaseTV.pass); + XMLUtils::GetString(pDatabase, "name", m_databaseTV.name); + XMLUtils::GetString(pDatabase, "key", m_databaseTV.key); + XMLUtils::GetString(pDatabase, "cert", m_databaseTV.cert); + XMLUtils::GetString(pDatabase, "ca", m_databaseTV.ca); + XMLUtils::GetString(pDatabase, "capath", m_databaseTV.capath); + XMLUtils::GetString(pDatabase, "ciphers", m_databaseTV.ciphers); + XMLUtils::GetBoolean(pDatabase, "compression", m_databaseTV.compression); + } + + pDatabase = pRootElement->FirstChildElement("epgdatabase"); + if (pDatabase) + { + XMLUtils::GetString(pDatabase, "type", m_databaseEpg.type); + XMLUtils::GetString(pDatabase, "host", m_databaseEpg.host); + XMLUtils::GetString(pDatabase, "port", m_databaseEpg.port); + XMLUtils::GetString(pDatabase, "user", m_databaseEpg.user); + XMLUtils::GetString(pDatabase, "pass", m_databaseEpg.pass); + XMLUtils::GetString(pDatabase, "name", m_databaseEpg.name); + XMLUtils::GetString(pDatabase, "key", m_databaseEpg.key); + XMLUtils::GetString(pDatabase, "cert", m_databaseEpg.cert); + XMLUtils::GetString(pDatabase, "ca", m_databaseEpg.ca); + XMLUtils::GetString(pDatabase, "capath", m_databaseEpg.capath); + XMLUtils::GetString(pDatabase, "ciphers", m_databaseEpg.ciphers); + XMLUtils::GetBoolean(pDatabase, "compression", m_databaseEpg.compression); + } + + pElement = pRootElement->FirstChildElement("enablemultimediakeys"); + if (pElement) + { + XMLUtils::GetBoolean(pRootElement, "enablemultimediakeys", m_enableMultimediaKeys); + } + + pElement = pRootElement->FirstChildElement("gui"); + if (pElement) + { + XMLUtils::GetBoolean(pElement, "visualizedirtyregions", m_guiVisualizeDirtyRegions); + XMLUtils::GetInt(pElement, "algorithmdirtyregions", m_guiAlgorithmDirtyRegions); + XMLUtils::GetBoolean(pElement, "smartredraw", m_guiSmartRedraw); + } + + std::string seekSteps; + XMLUtils::GetString(pRootElement, "seeksteps", seekSteps); + if (!seekSteps.empty()) + { + m_seekSteps.clear(); + std::vector<std::string> steps = StringUtils::Split(seekSteps, ','); + for(std::vector<std::string>::iterator it = steps.begin(); it != steps.end(); ++it) + m_seekSteps.push_back(atoi((*it).c_str())); + } + + XMLUtils::GetBoolean(pRootElement, "opengldebugging", m_openGlDebugging); + + // load in the settings overrides + CServiceBroker::GetSettingsComponent()->GetSettings()->LoadHidden(pRootElement); + + // Migration of old style art options from advanced setting to GUI setting + MigrateOldArtSettings(); +} + +void CAdvancedSettings::Clear() +{ + m_videoCleanStringRegExps.clear(); + m_moviesExcludeFromScanRegExps.clear(); + m_tvshowExcludeFromScanRegExps.clear(); + m_videoExcludeFromListingRegExps.clear(); + m_videoStackRegExps.clear(); + m_folderStackRegExps.clear(); + m_allExcludeFromScanRegExps.clear(); + m_audioExcludeFromScanRegExps.clear(); + m_audioExcludeFromListingRegExps.clear(); + m_pictureExcludeFromListingRegExps.clear(); + + m_pictureExtensions.clear(); + m_musicExtensions.clear(); + m_videoExtensions.clear(); + m_discStubExtensions.clear(); + + m_userAgent.clear(); +} + +void CAdvancedSettings::GetCustomTVRegexps(TiXmlElement *pRootElement, SETTINGS_TVSHOWLIST& settings) +{ + TiXmlElement *pElement = pRootElement; + while (pElement) + { + int iAction = 0; // overwrite + // for backward compatibility + const char* szAppend = pElement->Attribute("append"); + if ((szAppend && StringUtils::CompareNoCase(szAppend, "yes") == 0)) + iAction = 1; + // action takes precedence if both attributes exist + const char* szAction = pElement->Attribute("action"); + if (szAction) + { + iAction = 0; // overwrite + if (StringUtils::CompareNoCase(szAction, "append") == 0) + iAction = 1; // append + else if (StringUtils::CompareNoCase(szAction, "prepend") == 0) + iAction = 2; // prepend + } + if (iAction == 0) + settings.clear(); + TiXmlNode* pRegExp = pElement->FirstChild("regexp"); + int i = 0; + while (pRegExp) + { + if (pRegExp->FirstChild()) + { + bool bByDate = false; + bool byTitle = false; + int iDefaultSeason = 1; + if (pRegExp->ToElement()) + { + std::string byDate = XMLUtils::GetAttribute(pRegExp->ToElement(), "bydate"); + if (byDate == "true") + { + bByDate = true; + } + std::string byTitleAttr = XMLUtils::GetAttribute(pRegExp->ToElement(), "bytitle"); + byTitle = (byTitleAttr == "true"); + std::string defaultSeason = XMLUtils::GetAttribute(pRegExp->ToElement(), "defaultseason"); + if(!defaultSeason.empty()) + { + iDefaultSeason = atoi(defaultSeason.c_str()); + } + } + std::string regExp = pRegExp->FirstChild()->Value(); + if (iAction == 2) + { + settings.insert(settings.begin() + i++, 1, + TVShowRegexp(bByDate, regExp, iDefaultSeason, byTitle)); + } + else + { + settings.push_back(TVShowRegexp(bByDate, regExp, iDefaultSeason, byTitle)); + } + } + pRegExp = pRegExp->NextSibling("regexp"); + } + + pElement = pElement->NextSiblingElement(pRootElement->Value()); + } +} + +void CAdvancedSettings::GetCustomRegexps(TiXmlElement *pRootElement, std::vector<std::string>& settings) +{ + TiXmlElement *pElement = pRootElement; + while (pElement) + { + int iAction = 0; // overwrite + // for backward compatibility + const char* szAppend = pElement->Attribute("append"); + if ((szAppend && StringUtils::CompareNoCase(szAppend, "yes") == 0)) + iAction = 1; + // action takes precedence if both attributes exist + const char* szAction = pElement->Attribute("action"); + if (szAction) + { + iAction = 0; // overwrite + if (StringUtils::CompareNoCase(szAction, "append") == 0) + iAction = 1; // append + else if (StringUtils::CompareNoCase(szAction, "prepend") == 0) + iAction = 2; // prepend + } + if (iAction == 0) + settings.clear(); + TiXmlNode* pRegExp = pElement->FirstChild("regexp"); + int i = 0; + while (pRegExp) + { + if (pRegExp->FirstChild()) + { + std::string regExp = pRegExp->FirstChild()->Value(); + if (iAction == 2) + settings.insert(settings.begin() + i++, 1, regExp); + else + settings.push_back(regExp); + } + pRegExp = pRegExp->NextSibling("regexp"); + } + + pElement = pElement->NextSiblingElement(pRootElement->Value()); + } +} + +void CAdvancedSettings::GetCustomExtensions(TiXmlElement *pRootElement, std::string& extensions) +{ + std::string extraExtensions; + if (XMLUtils::GetString(pRootElement, "add", extraExtensions) && !extraExtensions.empty()) + extensions += "|" + extraExtensions; + if (XMLUtils::GetString(pRootElement, "remove", extraExtensions) && !extraExtensions.empty()) + { + std::vector<std::string> exts = StringUtils::Split(extraExtensions, '|'); + for (std::vector<std::string>::const_iterator i = exts.begin(); i != exts.end(); ++i) + { + size_t iPos = extensions.find(*i); + if (iPos != std::string::npos) + extensions.erase(iPos,i->size()+1); + } + } +} + +void CAdvancedSettings::AddSettingsFile(const std::string &filename) +{ + m_settingsFiles.push_back(filename); +} + +float CAdvancedSettings::GetLatencyTweak(float refreshrate) +{ + float delay = m_videoDefaultLatency; + for (int i = 0; i < (int) m_videoRefreshLatency.size(); i++) + { + RefreshVideoLatency& videolatency = m_videoRefreshLatency[i]; + if (refreshrate >= videolatency.refreshmin && refreshrate <= videolatency.refreshmax) + delay = videolatency.delay; + } + + return delay; // in milliseconds +} + +void CAdvancedSettings::SetDebugMode(bool debug) +{ + if (debug) + { + int level = std::max(m_logLevelHint, LOG_LEVEL_DEBUG_FREEMEM); + m_logLevel = level; + CServiceBroker::GetLogging().SetLogLevel(level); + CLog::Log(LOGINFO, "Enabled debug logging due to GUI setting. Level {}.", level); + } + else + { + int level = std::min(m_logLevelHint, LOG_LEVEL_DEBUG/*LOG_LEVEL_NORMAL*/); + CLog::Log(LOGINFO, "Disabled debug logging due to GUI setting. Level {}.", level); + m_logLevel = level; + CServiceBroker::GetLogging().SetLogLevel(level); + } +} + +void CAdvancedSettings::SetExtraArtwork(const TiXmlElement* arttypes, std::vector<std::string>& artworkMap) +{ + if (!arttypes) + return; + artworkMap.clear(); + const TiXmlNode* arttype = arttypes->FirstChild("arttype"); + while (arttype) + { + if (arttype->FirstChild()) + artworkMap.push_back(arttype->FirstChild()->ValueStr()); + arttype = arttype->NextSibling("arttype"); + } +} + +void ConvertToWhitelist(const std::vector<std::string>& oldlist, std::vector<CVariant>& whitelist) +{ + for (auto& it : oldlist) + { + size_t last_index = it.find_last_not_of("0123456789"); + std::string strFamilyType = it.substr(0, last_index + 1); // "fanart" of "fanart16" + if (std::find(whitelist.begin(), whitelist.end(), strFamilyType) == whitelist.end()) + whitelist.emplace_back(strFamilyType); + } +} + +void CAdvancedSettings::MigrateOldArtSettings() +{ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (!settings->GetBool(CSettings::SETTING_MUSICLIBRARY_ARTSETTINGS_UPDATED)) + { + CLog::Log(LOGINFO, "Migrating old music library artwork settings to new GUI settings"); + // Convert numeric art type variants into simple art type family entry + // e.g. {"banner", "fanart1", "fanart2", "fanart3"... } into { "banner", "fanart"} + if (!m_musicArtistExtraArt.empty()) + { + std::vector<CVariant> whitelist; + ConvertToWhitelist(m_musicArtistExtraArt, whitelist); + settings->SetList(CSettings::SETTING_MUSICLIBRARY_ARTISTART_WHITELIST, whitelist); + } + if (!m_musicAlbumExtraArt.empty()) + { + std::vector<CVariant> whitelist; + ConvertToWhitelist(m_musicAlbumExtraArt, whitelist); + settings->SetList(CSettings::SETTING_MUSICLIBRARY_ALBUMART_WHITELIST, whitelist); + } + + // Convert value like "folder.jpg|Folder.jpg|folder.JPG|Folder.JPG|cover.jpg|Cover.jpg| + // cover.jpeg|thumb.jpg|Thumb.jpg|thumb.JPG|Thumb.JPG" into case-insensitive unique elements + // e.g. {"folder.jpg", "cover.jpg", "cover.jpeg", "thumb.jpg"} + if (!m_musicThumbs.empty()) + { + std::vector<std::string> thumbs1 = StringUtils::Split(m_musicThumbs, "|"); + std::vector<std::string> thumbs2; + for (auto& it : thumbs1) + { + StringUtils::ToLower(it); + if (std::find(thumbs2.begin(), thumbs2.end(), it) == thumbs2.end()) + thumbs2.emplace_back(it); + } + std::vector<CVariant> thumbs; + thumbs.reserve(thumbs2.size()); + for (const auto& it : thumbs2) + thumbs.emplace_back(it); + settings->SetList(CSettings::SETTING_MUSICLIBRARY_MUSICTHUMBS, thumbs); + } + + // Whitelists configured, set artwork level to custom + if (!m_musicAlbumExtraArt.empty() || !m_musicArtistExtraArt.empty()) + settings->SetInt(CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL, 2); + + // Flag migration of settings so not done again + settings->SetBool(CSettings::SETTING_MUSICLIBRARY_ARTSETTINGS_UPDATED, true); + } + + if (!settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_ARTSETTINGS_UPDATED)) + { + CLog::Log(LOGINFO, "Migrating old video library artwork settings to new GUI settings"); + // Convert numeric art type variants into simple art type family entry + // e.g. {"banner", "fanart1", "fanart2", "fanart3"... } into { "banner", "fanart"} + if (!m_videoEpisodeExtraArt.empty()) + { + std::vector<CVariant> whitelist; + ConvertToWhitelist(m_videoEpisodeExtraArt, whitelist); + settings->SetList(CSettings::SETTING_VIDEOLIBRARY_EPISODEART_WHITELIST, whitelist); + } + if (!m_videoTvShowExtraArt.empty()) + { + std::vector<CVariant> whitelist; + ConvertToWhitelist(m_videoTvShowExtraArt, whitelist); + settings->SetList(CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST, whitelist); + } + if (!m_videoMovieExtraArt.empty()) + { + std::vector<CVariant> whitelist; + ConvertToWhitelist(m_videoMovieExtraArt, whitelist); + settings->SetList(CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST, whitelist); + } + if (!m_videoMusicVideoExtraArt.empty()) + { + std::vector<CVariant> whitelist; + ConvertToWhitelist(m_videoMusicVideoExtraArt, whitelist); + settings->SetList(CSettings::SETTING_VIDEOLIBRARY_MUSICVIDEOART_WHITELIST, whitelist); + } + + // Whitelists configured, set artwork level to custom + if (!m_videoEpisodeExtraArt.empty() || !m_videoTvShowExtraArt.empty() + || !m_videoMovieExtraArt.empty() || !m_videoMusicVideoExtraArt.empty()) + settings->SetInt(CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL, + CSettings::MUSICLIBRARY_ARTWORK_LEVEL_CUSTOM); + + // Flag migration of settings so not done again + settings->SetBool(CSettings::SETTING_VIDEOLIBRARY_ARTSETTINGS_UPDATED, true); + } +} diff --git a/xbmc/settings/AdvancedSettings.h b/xbmc/settings/AdvancedSettings.h new file mode 100644 index 0000000..4ba4e7e --- /dev/null +++ b/xbmc/settings/AdvancedSettings.h @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "pictures/PictureScalingAlgorithm.h" +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" +#include "utils/SortUtils.h" + +#include <set> +#include <string> +#include <utility> +#include <vector> + +#define CACHE_BUFFER_MODE_INTERNET 0 +#define CACHE_BUFFER_MODE_ALL 1 +#define CACHE_BUFFER_MODE_TRUE_INTERNET 2 +#define CACHE_BUFFER_MODE_NONE 3 +#define CACHE_BUFFER_MODE_NETWORK 4 + +class CProfileManager; +class CSettingsManager; +class CVariant; +struct IntegerSettingOption; + +class TiXmlElement; +namespace ADDON +{ + class IAddon; +} + +class DatabaseSettings +{ +public: + DatabaseSettings() { Reset(); } + void Reset() + { + type.clear(); + host.clear(); + port.clear(); + user.clear(); + pass.clear(); + name.clear(); + key.clear(); + cert.clear(); + ca.clear(); + capath.clear(); + ciphers.clear(); + compression = false; + }; + std::string type; + std::string host; + std::string port; + std::string user; + std::string pass; + std::string name; + std::string key; + std::string cert; + std::string ca; + std::string capath; + std::string ciphers; + bool compression; +}; + +struct TVShowRegexp +{ + bool byDate; + bool byTitle; + std::string regexp; + int defaultSeason; + TVShowRegexp(bool d, const std::string& r, int s = 1, bool t = false) : regexp(r) + { + byDate = d; + defaultSeason = s; + byTitle = t; + } +}; + +struct RefreshOverride +{ + float fpsmin; + float fpsmax; + + float refreshmin; + float refreshmax; + + bool fallback; +}; + + +struct RefreshVideoLatency +{ + float refreshmin; + float refreshmax; + + float delay; +}; + +typedef std::vector<TVShowRegexp> SETTINGS_TVSHOWLIST; + +class CAdvancedSettings : public ISettingCallback, public ISettingsHandler +{ + public: + CAdvancedSettings(); + + void OnSettingsLoaded() override; + void OnSettingsUnloaded() override; + + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + void Initialize(CSettingsManager& settingsMgr); + void Uninitialize(CSettingsManager& settingsMgr); + bool Initialized() const { return m_initialized; } + void AddSettingsFile(const std::string &filename); + bool Load(const CProfileManager &profileManager); + + static void GetCustomTVRegexps(TiXmlElement *pRootElement, SETTINGS_TVSHOWLIST& settings); + static void GetCustomRegexps(TiXmlElement *pRootElement, std::vector<std::string> &settings); + static void GetCustomExtensions(TiXmlElement *pRootElement, std::string& extensions); + + std::string m_audioDefaultPlayer; + float m_audioPlayCountMinimumPercent; + bool m_VideoPlayerIgnoreDTSinWAV; + float m_limiterHold; + float m_limiterRelease; + + bool m_omlSync = true; + + float m_videoSubsDelayRange; + float m_videoAudioDelayRange; + bool m_videoUseTimeSeeking; + int m_videoTimeSeekForward; + int m_videoTimeSeekBackward; + int m_videoTimeSeekForwardBig; + int m_videoTimeSeekBackwardBig; + int m_videoPercentSeekForward; + int m_videoPercentSeekBackward; + int m_videoPercentSeekForwardBig; + int m_videoPercentSeekBackwardBig; + std::vector<int> m_seekSteps; + std::string m_videoPPFFmpegPostProc; + bool m_videoVDPAUtelecine; + bool m_videoVDPAUdeintSkipChromaHD; + bool m_musicUseTimeSeeking; + int m_musicTimeSeekForward; + int m_musicTimeSeekBackward; + int m_musicTimeSeekForwardBig; + int m_musicTimeSeekBackwardBig; + int m_musicPercentSeekForward; + int m_musicPercentSeekBackward; + int m_musicPercentSeekForwardBig; + int m_musicPercentSeekBackwardBig; + int m_videoIgnoreSecondsAtStart; + float m_videoIgnorePercentAtEnd; + float m_audioApplyDrc; + unsigned int m_maxPassthroughOffSyncDuration = 10; // when 10 ms off adjust + + int m_videoVDPAUScaling; + float m_videoNonLinStretchRatio; + float m_videoAutoScaleMaxFps; + std::vector<RefreshOverride> m_videoAdjustRefreshOverrides; + std::vector<RefreshVideoLatency> m_videoRefreshLatency; + float m_videoDefaultLatency; + int m_videoCaptureUseOcclusionQuery; + bool m_DXVACheckCompatibility; + bool m_DXVACheckCompatibilityPresent; + int m_videoFpsDetect; + float m_maxTempo; + bool m_videoPreferStereoStream = false; + + std::string m_videoDefaultPlayer; + float m_videoPlayCountMinimumPercent; + + float m_slideshowBlackBarCompensation; + float m_slideshowZoomAmount; + float m_slideshowPanAmount; + + int m_songInfoDuration; + int m_logLevel; + int m_logLevelHint; + std::string m_cddbAddress; + bool m_addSourceOnTop; //!< True to put 'add source' buttons on top + + //airtunes + airplay + int m_airTunesPort; + int m_airPlayPort; + + /*! \brief Only used in linux for the udisks and udisks2 providers + * defines if kodi should automount media drives + * @note if kodi is running standalone (--standalone option) it will + * be set to tue + */ + bool m_handleMounting; + /*! \brief Only used in linux for the udisks and udisks2 providers + * defines if kodi should automount optical discs + */ + bool m_autoMountOpticalMedia{true}; + + bool m_fullScreenOnMovieStart; + std::string m_cachePath; + std::string m_videoCleanDateTimeRegExp; + std::vector<std::string> m_videoCleanStringRegExps; + std::vector<std::string> m_videoExcludeFromListingRegExps; + std::vector<std::string> m_allExcludeFromScanRegExps; + std::vector<std::string> m_moviesExcludeFromScanRegExps; + std::vector<std::string> m_tvshowExcludeFromScanRegExps; + std::vector<std::string> m_audioExcludeFromListingRegExps; + std::vector<std::string> m_audioExcludeFromScanRegExps; + std::vector<std::string> m_pictureExcludeFromListingRegExps; + std::vector<std::string> m_videoStackRegExps; + std::vector<std::string> m_folderStackRegExps; + std::vector<std::string> m_trailerMatchRegExps; + SETTINGS_TVSHOWLIST m_tvshowEnumRegExps; + std::string m_tvshowMultiPartEnumRegExp; + typedef std::vector< std::pair<std::string, std::string> > StringMapping; + StringMapping m_pathSubstitutions; + int m_remoteDelay; ///< \brief number of remote messages to ignore before repeating + bool m_bScanIRServer; + + bool m_playlistAsFolders; + bool m_detectAsUdf; + + unsigned int m_fanartRes; ///< \brief the maximal resolution to cache fanart at (assumes 16x9) + unsigned int m_imageRes; ///< \brief the maximal resolution to cache images at (assumes 16x9) + CPictureScalingAlgorithm::Algorithm m_imageScalingAlgorithm; + unsigned int + m_imageQualityJpeg; ///< \brief the stored jpeg quality the lower the better (default: 4) + + int m_sambaclienttimeout; + std::string m_sambadoscodepage; + bool m_sambastatfiles; + + bool m_bHTTPDirectoryStatFilesize; + + bool m_bFTPThumbs; + bool m_bShoutcastArt; + + std::string m_musicThumbs; + std::vector<std::string> m_musicArtistExtraArt; + std::vector<std::string> m_musicAlbumExtraArt; + + int m_iMusicLibraryRecentlyAddedItems; + int m_iMusicLibraryDateAdded; + bool m_bMusicLibraryAllItemsOnBottom; + bool m_bMusicLibraryCleanOnUpdate; + bool m_bMusicLibraryArtistSortOnUpdate; + bool m_bMusicLibraryUseISODates; + std::string m_strMusicLibraryAlbumFormat; + bool m_prioritiseAPEv2tags; + std::string m_musicItemSeparator; + std::vector<std::string> m_musicArtistSeparators; + std::string m_videoItemSeparator; + std::vector<std::string> m_musicTagsFromFileFilters; + + bool m_bVideoLibraryAllItemsOnBottom; + int m_iVideoLibraryRecentlyAddedItems; + bool m_bVideoLibraryCleanOnUpdate; + bool m_bVideoLibraryUseFastHash; + bool m_bVideoLibraryImportWatchedState{true}; + bool m_bVideoLibraryImportResumePoint{true}; + std::vector<std::string> m_videoEpisodeExtraArt; + std::vector<std::string> m_videoTvShowExtraArt; + std::vector<std::string> m_videoTvSeasonExtraArt; + std::vector<std::string> m_videoMovieExtraArt; + std::vector<std::string> m_videoMovieSetExtraArt; + std::vector<std::string> m_videoMusicVideoExtraArt; + + bool m_bVideoScannerIgnoreErrors; + int m_iVideoLibraryDateAdded; + + std::set<std::string> m_vecTokens; + + int m_iEpgUpdateCheckInterval; // seconds + int m_iEpgCleanupInterval; // seconds + int m_iEpgActiveTagCheckInterval; // seconds + int m_iEpgRetryInterruptedUpdateInterval; // seconds + int m_iEpgUpdateEmptyTagsInterval; // seconds + bool m_bEpgDisplayUpdatePopup; + bool m_bEpgDisplayIncrementalUpdatePopup; + + // EDL Commercial Break + bool m_bEdlMergeShortCommBreaks; + /*!< @brief If GUI notifications should be shown when reaching the start of commercial breaks */ + bool m_EdlDisplayCommbreakNotifications; + int m_iEdlMaxCommBreakLength; // seconds + int m_iEdlMinCommBreakLength; // seconds + int m_iEdlMaxCommBreakGap; // seconds + int m_iEdlMaxStartGap; // seconds + int m_iEdlCommBreakAutowait; // seconds + int m_iEdlCommBreakAutowind; // seconds + + int m_curlconnecttimeout; + int m_curllowspeedtime; + int m_curlretries; + int m_curlKeepAliveInterval; // seconds + bool m_curlDisableIPV6; + bool m_curlDisableHTTP2; + + std::string m_caTrustFile; + + bool m_minimizeToTray; /* win32 only */ + bool m_fullScreen; + bool m_startFullScreen; + bool m_showExitButton; /* Ideal for appliances to hide a 'useless' button */ + bool m_canWindowed; + bool m_splashImage; + bool m_alwaysOnTop; /* makes xbmc to run always on top .. osx/win32 only .. */ + int m_playlistRetries; + int m_playlistTimeout; + bool m_GLRectangleHack; + int m_iSkipLoopFilter; + + bool m_bVirtualShares; + + std::string m_cpuTempCmd; + std::string m_gpuTempCmd; + + /* PVR/TV related advanced settings */ + int m_iPVRTimeCorrection; /*!< @brief correct all times (epg tags, timer tags, recording tags) by this amount of minutes. defaults to 0. */ + int m_iPVRInfoToggleInterval; /*!< @brief if there are more than 1 pvr gui info item available (e.g. multiple recordings active at the same time), use this toggle delay in milliseconds. defaults to 3000. */ + bool m_bPVRChannelIconsAutoScan; /*!< @brief automatically scan user defined folder for channel icons when loading internal channel groups */ + bool m_bPVRAutoScanIconsUserSet; /*!< @brief mark channel icons populated by auto scan as "user set" */ + int m_iPVRNumericChannelSwitchTimeout; /*!< @brief time in msecs after that a channel switch occurs after entering a channel number, if confirmchannelswitch is disabled */ + int m_iPVRTimeshiftThreshold; /*!< @brief time diff between current playing time and timeshift buffer end, in seconds, before a playing stream is displayed as timeshifting. */ + bool m_bPVRTimeshiftSimpleOSD; /*!< @brief use simple timeshift OSD (with progress only for the playing event instead of progress for the whole ts buffer). */ + SortDescription m_PVRDefaultSortOrder; /*!< @brief SortDecription used to store default recording sort type and sort order */ + + DatabaseSettings m_databaseMusic; // advanced music database setup + DatabaseSettings m_databaseVideo; // advanced video database setup + DatabaseSettings m_databaseTV; // advanced tv database setup + DatabaseSettings m_databaseEpg; /*!< advanced EPG database setup */ + + bool m_useLocaleCollation; + + bool m_guiVisualizeDirtyRegions; + int m_guiAlgorithmDirtyRegions; + bool m_guiSmartRedraw; + unsigned int m_addonPackageFolderSize; + + unsigned int m_cacheMemSize; + unsigned int m_cacheBufferMode; + unsigned int m_cacheChunkSize; + float m_cacheReadFactor; + + bool m_jsonOutputCompact; + unsigned int m_jsonTcpPort; + + bool m_enableMultimediaKeys; + std::vector<std::string> m_settingsFiles; + void ParseSettingsFile(const std::string &file); + + float GetLatencyTweak(float refreshrate); + bool m_initialized; + + void SetDebugMode(bool debug); + + //! \brief Toggles dirty-region visualization + void ToggleDirtyRegionVisualization() + { + m_guiVisualizeDirtyRegions = !m_guiVisualizeDirtyRegions; + } + + // runtime settings which cannot be set from advancedsettings.xml + std::string m_videoExtensions; + std::string m_discStubExtensions; + std::string m_subtitlesExtensions; + std::string m_musicExtensions; + std::string m_pictureExtensions; + + std::string m_stereoscopicregex_3d; + std::string m_stereoscopicregex_sbs; + std::string m_stereoscopicregex_tab; + + bool m_openGlDebugging; + + std::string m_userAgent; + uint32_t m_nfsTimeout; + int m_nfsRetries; + + private: + void Initialize(); + void Clear(); + void SetExtraArtwork(const TiXmlElement* arttypes, std::vector<std::string>& artworkMap); + void MigrateOldArtSettings(); +}; diff --git a/xbmc/settings/CMakeLists.txt b/xbmc/settings/CMakeLists.txt new file mode 100644 index 0000000..f53d12b --- /dev/null +++ b/xbmc/settings/CMakeLists.txt @@ -0,0 +1,49 @@ +set(SOURCES AdvancedSettings.cpp + DisplaySettings.cpp + GameSettings.cpp + LibExportSettings.cpp + MediaSettings.cpp + MediaSourceSettings.cpp + SettingAddon.cpp + SettingConditions.cpp + SettingControl.cpp + SettingCreator.cpp + SettingDateTime.cpp + SettingPath.cpp + Settings.cpp + SettingsBase.cpp + SettingsValueFlatJsonSerializer.cpp + SettingsValueXmlSerializer.cpp + SettingUtils.cpp + SkinSettings.cpp + SettingsComponent.cpp + SubtitlesSettings.cpp) + +set(HEADERS AdvancedSettings.h + DiscSettings.h + DisplaySettings.h + GameSettings.h + ISubSettings.h + LibExportSettings.h + MediaSettings.h + MediaSourceSettings.h + SettingAddon.h + SettingConditions.h + SettingControl.h + SettingCreator.h + SettingDateTime.h + SettingPath.h + Settings.h + SettingsBase.h + SettingsValueFlatJsonSerializer.h + SettingsValueXmlSerializer.h + SettingUtils.h + SkinSettings.h + SettingsComponent.h + SubtitlesSettings.h) + +if(BLURAY_FOUND) + list(APPEND SOURCES DiscSettings.cpp) +endif() + +core_add_library(settings) diff --git a/xbmc/settings/DiscSettings.cpp b/xbmc/settings/DiscSettings.cpp new file mode 100644 index 0000000..de8bb33 --- /dev/null +++ b/xbmc/settings/DiscSettings.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DiscSettings.h" + +#include "Settings.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "lib/Setting.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <string> + +#include <libbluray/bluray-version.h> +#include <libbluray/bluray.h> + +using namespace KODI::MESSAGING; + +CDiscSettings& CDiscSettings::GetInstance() +{ + static CDiscSettings sDiscSettings; + return sDiscSettings; +} + +void CDiscSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ +#if (BLURAY_VERSION >= BLURAY_VERSION_CODE(1,0,1)) + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_DISC_PLAYBACK) + { + int mode = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + if (mode == BD_PLAYBACK_DISC_MENU) + { + bool bdjWorking = false; + BLURAY* bd = bd_init(); + const BLURAY_DISC_INFO* info = bd_get_disc_info(bd); + + if (!info->libjvm_detected) + CLog::Log(LOGDEBUG, "DiscSettings - Could not load the java vm."); + else if (!info->bdj_handled) + CLog::Log(LOGDEBUG, "DiscSettings - Could not load the libbluray.jar."); + else + bdjWorking = true; + + bd_close(bd); + + if (!bdjWorking) + HELPERS::ShowOKDialogText(CVariant{ 29803 }, CVariant{ 29804 }); + } + } +#endif +} diff --git a/xbmc/settings/DiscSettings.h b/xbmc/settings/DiscSettings.h new file mode 100644 index 0000000..9d62eea --- /dev/null +++ b/xbmc/settings/DiscSettings.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/** +* Playback settings +*/ +enum BDPlaybackMode +{ + BD_PLAYBACK_SIMPLE_MENU = 0, + BD_PLAYBACK_DISC_MENU, + BD_PLAYBACK_MAIN_TITLE, +}; + +#include "settings/lib/ISettingCallback.h" + +class CDiscSettings : public ISettingCallback +{ +public: + /* ISettingCallback*/ + + static CDiscSettings& GetInstance(); + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + +private: + CDiscSettings() = default; + ~CDiscSettings() override = default; +}; diff --git a/xbmc/settings/DisplaySettings.cpp b/xbmc/settings/DisplaySettings.cpp new file mode 100644 index 0000000..25f230a --- /dev/null +++ b/xbmc/settings/DisplaySettings.cpp @@ -0,0 +1,948 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "DisplaySettings.h" + +#include "ServiceBroker.h" +#include "cores/VideoPlayer/VideoRenderers/ColorManager.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "guilib/GUIComponent.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/StereoscopicsManager.h" +#include "messaging/helpers/DialogHelper.h" +#include "rendering/RenderSystem.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "storage/MediaManager.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" +#include "windowing/WinSystem.h" + +#include <algorithm> +#include <cstdlib> +#include <float.h> +#include <mutex> +#include <string> +#include <utility> +#include <vector> + +#ifdef TARGET_WINDOWS +#include "rendering/dx/DeviceResources.h" +#endif + +using namespace KODI::MESSAGING; + +using KODI::MESSAGING::HELPERS::DialogResponse; + +// 0.1 second increments +#define MAX_REFRESH_CHANGE_DELAY 200 + +static RESOLUTION_INFO EmptyResolution; +static RESOLUTION_INFO EmptyModifiableResolution; + +float square_error(float x, float y) +{ + float yonx = (x > 0) ? y / x : 0; + float xony = (y > 0) ? x / y : 0; + return std::max(yonx, xony); +} + +static std::string ModeFlagsToString(unsigned int flags, bool identifier) +{ + std::string res; + if(flags & D3DPRESENTFLAG_INTERLACED) + res += "i"; + else + res += "p"; + + if(!identifier) + res += " "; + + if(flags & D3DPRESENTFLAG_MODE3DSBS) + res += "sbs"; + else if(flags & D3DPRESENTFLAG_MODE3DTB) + res += "tab"; + else if(identifier) + res += "std"; + return res; +} + +CDisplaySettings::CDisplaySettings() +{ + m_resolutions.resize(RES_CUSTOM); + + m_zoomAmount = 1.0f; + m_pixelRatio = 1.0f; + m_verticalShift = 0.0f; + m_nonLinearStretched = false; + m_resolutionChangeAborted = false; +} + +CDisplaySettings::~CDisplaySettings() = default; + +CDisplaySettings& CDisplaySettings::GetInstance() +{ + static CDisplaySettings sDisplaySettings; + return sDisplaySettings; +} + +bool CDisplaySettings::Load(const TiXmlNode *settings) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + m_calibrations.clear(); + + if (settings == NULL) + return false; + + const TiXmlElement *pElement = settings->FirstChildElement("resolutions"); + if (!pElement) + { + CLog::Log(LOGERROR, "CDisplaySettings: settings file doesn't contain <resolutions>"); + return false; + } + + const TiXmlElement *pResolution = pElement->FirstChildElement("resolution"); + while (pResolution) + { + // get the data for this calibration + RESOLUTION_INFO cal; + + XMLUtils::GetString(pResolution, "description", cal.strMode); + XMLUtils::GetInt(pResolution, "subtitles", cal.iSubtitles); + XMLUtils::GetFloat(pResolution, "pixelratio", cal.fPixelRatio); +#ifdef HAVE_X11 + XMLUtils::GetFloat(pResolution, "refreshrate", cal.fRefreshRate); + XMLUtils::GetString(pResolution, "output", cal.strOutput); + XMLUtils::GetString(pResolution, "xrandrid", cal.strId); +#endif + + const TiXmlElement *pOverscan = pResolution->FirstChildElement("overscan"); + if (pOverscan) + { + XMLUtils::GetInt(pOverscan, "left", cal.Overscan.left); + XMLUtils::GetInt(pOverscan, "top", cal.Overscan.top); + XMLUtils::GetInt(pOverscan, "right", cal.Overscan.right); + XMLUtils::GetInt(pOverscan, "bottom", cal.Overscan.bottom); + } + + // mark calibration as not updated + // we must not delete those, resolution just might not be available + cal.iWidth = cal.iHeight = 0; + + // store calibration, avoid adding duplicates + bool found = false; + for (ResolutionInfos::const_iterator it = m_calibrations.begin(); it != m_calibrations.end(); ++it) + { + if (StringUtils::EqualsNoCase(it->strMode, cal.strMode)) + { + found = true; + break; + } + } + if (!found) + m_calibrations.push_back(cal); + + // iterate around + pResolution = pResolution->NextSiblingElement("resolution"); + } + + ApplyCalibrations(); + return true; +} + +bool CDisplaySettings::Save(TiXmlNode *settings) const +{ + if (settings == NULL) + return false; + + std::unique_lock<CCriticalSection> lock(m_critical); + TiXmlElement xmlRootElement("resolutions"); + TiXmlNode *pRoot = settings->InsertEndChild(xmlRootElement); + if (pRoot == NULL) + return false; + + // save calibrations + for (ResolutionInfos::const_iterator it = m_calibrations.begin(); it != m_calibrations.end(); ++it) + { + // Write the resolution tag + TiXmlElement resElement("resolution"); + TiXmlNode *pNode = pRoot->InsertEndChild(resElement); + if (pNode == NULL) + return false; + + // Now write each of the pieces of information we need... + XMLUtils::SetString(pNode, "description", it->strMode); + XMLUtils::SetInt(pNode, "subtitles", it->iSubtitles); + XMLUtils::SetFloat(pNode, "pixelratio", it->fPixelRatio); +#ifdef HAVE_X11 + XMLUtils::SetFloat(pNode, "refreshrate", it->fRefreshRate); + XMLUtils::SetString(pNode, "output", it->strOutput); + XMLUtils::SetString(pNode, "xrandrid", it->strId); +#endif + + // create the overscan child + TiXmlElement overscanElement("overscan"); + TiXmlNode *pOverscanNode = pNode->InsertEndChild(overscanElement); + if (pOverscanNode == NULL) + return false; + + XMLUtils::SetInt(pOverscanNode, "left", it->Overscan.left); + XMLUtils::SetInt(pOverscanNode, "top", it->Overscan.top); + XMLUtils::SetInt(pOverscanNode, "right", it->Overscan.right); + XMLUtils::SetInt(pOverscanNode, "bottom", it->Overscan.bottom); + } + + return true; +} + +void CDisplaySettings::Clear() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + m_calibrations.clear(); + m_resolutions.clear(); + m_resolutions.resize(RES_CUSTOM); + + m_zoomAmount = 1.0f; + m_pixelRatio = 1.0f; + m_verticalShift = 0.0f; + m_nonLinearStretched = false; +} + +void CDisplaySettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == "videoscreen.cms3dlut") + { + std::string path = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + if (CGUIDialogFileBrowser::ShowAndGetFile(shares, ".3dlut", g_localizeStrings.Get(36580), path)) + { + std::static_pointer_cast<CSettingString>(std::const_pointer_cast<CSetting>(setting))->SetValue(path); + } + } + else if (settingId == "videoscreen.displayprofile") + { + std::string path = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + if (CGUIDialogFileBrowser::ShowAndGetFile(shares, ".icc|.icm", g_localizeStrings.Get(36581), path)) + { + std::static_pointer_cast<CSettingString>(std::const_pointer_cast<CSetting>(setting))->SetValue(path); + } + } +} + +bool CDisplaySettings::OnSettingChanging(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return false; + + const std::string &settingId = setting->GetId(); + if (settingId == CSettings::SETTING_VIDEOSCREEN_RESOLUTION || + settingId == CSettings::SETTING_VIDEOSCREEN_SCREEN) + { + RESOLUTION newRes = RES_DESKTOP; + if (settingId == CSettings::SETTING_VIDEOSCREEN_RESOLUTION) + newRes = (RESOLUTION)std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + else if (settingId == CSettings::SETTING_VIDEOSCREEN_SCREEN) + { + int screen = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + + // if triggered by a change of screenmode, screen may not have changed + if (screen == GetCurrentDisplayMode()) + return true; + + // get desktop resolution for screen + newRes = GetResolutionForScreen(); + } + + std::string screenmode = GetStringFromResolution(newRes); + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_VIDEOSCREEN_SCREENMODE, screenmode)) + return false; + } + + if (settingId == CSettings::SETTING_VIDEOSCREEN_SCREENMODE) + { + RESOLUTION oldRes = GetCurrentResolution(); + RESOLUTION newRes = GetResolutionFromString(std::static_pointer_cast<const CSettingString>(setting)->GetValue()); + + SetCurrentResolution(newRes, false); + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(newRes, false); + + // check if the old or the new resolution was/is windowed + // in which case we don't show any prompt to the user + if (oldRes != RES_WINDOW && newRes != RES_WINDOW && oldRes != newRes) + { + if (!m_resolutionChangeAborted) + { + if (HELPERS::ShowYesNoDialogText(CVariant{13110}, CVariant{13111}, CVariant{""}, + CVariant{""}, 15000) != DialogResponse::CHOICE_YES) + { + m_resolutionChangeAborted = true; + return false; + } + } + else + m_resolutionChangeAborted = false; + } + } + else if (settingId == CSettings::SETTING_VIDEOSCREEN_MONITOR) + { + CServiceBroker::GetWinSystem()->UpdateResolutions(); + RESOLUTION newRes = GetResolutionForScreen(); + + SetCurrentResolution(newRes, false); + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(newRes, true); + + if (!m_resolutionChangeAborted) + { + if (HELPERS::ShowYesNoDialogText(CVariant{13110}, CVariant{13111}, CVariant{""}, CVariant{""}, + 10000) != DialogResponse::CHOICE_YES) + { + m_resolutionChangeAborted = true; + return false; + } + } + else + m_resolutionChangeAborted = false; + + return true; + } + else if (settingId == CSettings::SETTING_VIDEOSCREEN_10BITSURFACES) + { +#ifdef TARGET_WINDOWS + DX::DeviceResources::Get()->ApplyDisplaySettings(); + return true; +#endif + } +#if defined(HAVE_X11) || defined(TARGET_WINDOWS_DESKTOP) || defined(TARGET_DARWIN_OSX) + else if (settingId == CSettings::SETTING_VIDEOSCREEN_BLANKDISPLAYS) + { + auto winSystem = CServiceBroker::GetWinSystem(); +#if defined(HAVE_X11) + winSystem->UpdateResolutions(); +#elif defined(TARGET_WINDOWS_DESKTOP) || defined(TARGET_DARWIN_OSX) + CGraphicContext& gfxContext = winSystem->GetGfxContext(); + gfxContext.SetVideoResolution(gfxContext.GetVideoResolution(), true); +#endif + } +#endif + + return true; +} + +bool CDisplaySettings::OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) +{ + if (setting == NULL) + return false; + + const std::string &settingId = setting->GetId(); + if (settingId == CSettings::SETTING_VIDEOSCREEN_SCREENMODE) + { + std::shared_ptr<CSettingString> screenmodeSetting = std::static_pointer_cast<CSettingString>(setting); + std::string screenmode = screenmodeSetting->GetValue(); + // in Eden there was no character ("i" or "p") indicating interlaced/progressive + // at the end so we just add a "p" and assume progressive + // no 3d mode existed before, so just assume std modes + if (screenmode.size() == 20) + return screenmodeSetting->SetValue(screenmode + "pstd"); + if (screenmode.size() == 21) + return screenmodeSetting->SetValue(screenmode + "std"); + } + else if (settingId == CSettings::SETTING_VIDEOSCREEN_PREFEREDSTEREOSCOPICMODE) + { + std::shared_ptr<CSettingInt> stereomodeSetting = std::static_pointer_cast<CSettingInt>(setting); + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + STEREOSCOPIC_PLAYBACK_MODE playbackMode = (STEREOSCOPIC_PLAYBACK_MODE) settings->GetInt(CSettings::SETTING_VIDEOPLAYER_STEREOSCOPICPLAYBACKMODE); + if (stereomodeSetting->GetValue() == RENDER_STEREO_MODE_OFF) + { + // if preferred playback mode was OFF, update playback mode to ignore + if (playbackMode == STEREOSCOPIC_PLAYBACK_MODE_PREFERRED) + settings->SetInt(CSettings::SETTING_VIDEOPLAYER_STEREOSCOPICPLAYBACKMODE, STEREOSCOPIC_PLAYBACK_MODE_IGNORE); + return stereomodeSetting->SetValue(RENDER_STEREO_MODE_AUTO); + } + else if (stereomodeSetting->GetValue() == RENDER_STEREO_MODE_MONO) + { + // if preferred playback mode was MONO, update playback mode + if (playbackMode == STEREOSCOPIC_PLAYBACK_MODE_PREFERRED) + settings->SetInt(CSettings::SETTING_VIDEOPLAYER_STEREOSCOPICPLAYBACKMODE, STEREOSCOPIC_PLAYBACK_MODE_MONO); + return stereomodeSetting->SetValue(RENDER_STEREO_MODE_AUTO); + } + } + + return false; +} + +void CDisplaySettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (!setting) + return; + + const std::string& settingId = setting->GetId(); + if (settingId == CSettings::SETTING_VIDEOSCREEN_WHITELIST) + CResolutionUtils::PrintWhitelist(); +} + +void CDisplaySettings::SetMonitor(const std::string& monitor) +{ + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + const std::string curMonitor = settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR); + if (curMonitor != monitor) + { + m_resolutionChangeAborted = true; + settings->SetString(CSettings::SETTING_VIDEOSCREEN_MONITOR, monitor); + } +} + +void CDisplaySettings::SetCurrentResolution(RESOLUTION resolution, bool save /* = false */) +{ + if (resolution == RES_WINDOW && !CServiceBroker::GetWinSystem()->CanDoWindowed()) + resolution = RES_DESKTOP; + + if (save) + { + // Save videoscreen.screenmode setting + std::string mode = GetStringFromResolution(resolution); + CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(CSettings::SETTING_VIDEOSCREEN_SCREENMODE, mode.c_str()); + + // Check if videoscreen.screen setting also needs to be saved + // e.g. if ToggleFullscreen is called + int currentDisplayMode = GetCurrentDisplayMode(); + int currentDisplayModeSetting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOSCREEN_SCREEN); + if (currentDisplayMode != currentDisplayModeSetting) + { + CServiceBroker::GetSettingsComponent()->GetSettings()->SetInt(CSettings::SETTING_VIDEOSCREEN_SCREEN, currentDisplayMode); + } + } + else if (resolution != m_currentResolution) + { + m_currentResolution = resolution; + SetChanged(); + } +} + +RESOLUTION CDisplaySettings::GetDisplayResolution() const +{ + return GetResolutionFromString(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_SCREENMODE)); +} + +const RESOLUTION_INFO& CDisplaySettings::GetResolutionInfo(size_t index) const +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (index >= m_resolutions.size()) + return EmptyResolution; + + return m_resolutions[index]; +} + +const RESOLUTION_INFO& CDisplaySettings::GetResolutionInfo(RESOLUTION resolution) const +{ + if (resolution <= RES_INVALID) + return EmptyResolution; + + return GetResolutionInfo((size_t)resolution); +} + +RESOLUTION_INFO& CDisplaySettings::GetResolutionInfo(size_t index) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (index >= m_resolutions.size()) + { + EmptyModifiableResolution = RESOLUTION_INFO(); + return EmptyModifiableResolution; + } + + return m_resolutions[index]; +} + +RESOLUTION_INFO& CDisplaySettings::GetResolutionInfo(RESOLUTION resolution) +{ + if (resolution <= RES_INVALID) + { + EmptyModifiableResolution = RESOLUTION_INFO(); + return EmptyModifiableResolution; + } + + return GetResolutionInfo((size_t)resolution); +} + +void CDisplaySettings::AddResolutionInfo(const RESOLUTION_INFO &resolution) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + RESOLUTION_INFO res(resolution); + + if((res.dwFlags & D3DPRESENTFLAG_MODE3DTB) == 0) + { + /* add corrections for some special case modes frame packing modes */ + + if(res.iScreenWidth == 1920 + && res.iScreenHeight == 2205) + { + res.iBlanking = 45; + res.dwFlags |= D3DPRESENTFLAG_MODE3DTB; + } + + if(res.iScreenWidth == 1280 + && res.iScreenHeight == 1470) + { + res.iBlanking = 30; + res.dwFlags |= D3DPRESENTFLAG_MODE3DTB; + } + } + m_resolutions.push_back(res); +} + +void CDisplaySettings::ApplyCalibrations() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + // apply all calibrations to the resolutions + for (ResolutionInfos::const_iterator itCal = m_calibrations.begin(); itCal != m_calibrations.end(); ++itCal) + { + // find resolutions + for (size_t res = RES_DESKTOP; res < m_resolutions.size(); ++res) + { + if (StringUtils::EqualsNoCase(itCal->strMode, m_resolutions[res].strMode)) + { + // overscan + m_resolutions[res].Overscan.left = itCal->Overscan.left; + if (m_resolutions[res].Overscan.left < -m_resolutions[res].iWidth/4) + m_resolutions[res].Overscan.left = -m_resolutions[res].iWidth/4; + if (m_resolutions[res].Overscan.left > m_resolutions[res].iWidth/4) + m_resolutions[res].Overscan.left = m_resolutions[res].iWidth/4; + + m_resolutions[res].Overscan.top = itCal->Overscan.top; + if (m_resolutions[res].Overscan.top < -m_resolutions[res].iHeight/4) + m_resolutions[res].Overscan.top = -m_resolutions[res].iHeight/4; + if (m_resolutions[res].Overscan.top > m_resolutions[res].iHeight/4) + m_resolutions[res].Overscan.top = m_resolutions[res].iHeight/4; + + m_resolutions[res].Overscan.right = itCal->Overscan.right; + if (m_resolutions[res].Overscan.right < m_resolutions[res].iWidth / 2) + m_resolutions[res].Overscan.right = m_resolutions[res].iWidth / 2; + if (m_resolutions[res].Overscan.right > m_resolutions[res].iWidth * 3/2) + m_resolutions[res].Overscan.right = m_resolutions[res].iWidth *3/2; + + m_resolutions[res].Overscan.bottom = itCal->Overscan.bottom; + if (m_resolutions[res].Overscan.bottom < m_resolutions[res].iHeight / 2) + m_resolutions[res].Overscan.bottom = m_resolutions[res].iHeight / 2; + if (m_resolutions[res].Overscan.bottom > m_resolutions[res].iHeight * 3/2) + m_resolutions[res].Overscan.bottom = m_resolutions[res].iHeight * 3/2; + + m_resolutions[res].iSubtitles = itCal->iSubtitles; + if (m_resolutions[res].iSubtitles < 0) + m_resolutions[res].iSubtitles = 0; + if (m_resolutions[res].iSubtitles > m_resolutions[res].iHeight * 3 / 2) + m_resolutions[res].iSubtitles = m_resolutions[res].iHeight * 3 / 2; + + m_resolutions[res].fPixelRatio = itCal->fPixelRatio; + if (m_resolutions[res].fPixelRatio < 0.5f) + m_resolutions[res].fPixelRatio = 0.5f; + if (m_resolutions[res].fPixelRatio > 2.0f) + m_resolutions[res].fPixelRatio = 2.0f; + break; + } + } + } +} + +void CDisplaySettings::UpdateCalibrations() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + + if (m_resolutions.size() <= RES_DESKTOP) + return; + + // Add new (unique) resolutions + for (ResolutionInfos::const_iterator res(m_resolutions.cbegin() + RES_CUSTOM); res != m_resolutions.cend(); ++res) + if (std::find_if(m_calibrations.cbegin(), m_calibrations.cend(), + [&](const RESOLUTION_INFO& info) { return StringUtils::EqualsNoCase(res->strMode, info.strMode); }) == m_calibrations.cend()) + m_calibrations.push_back(*res); + + for (auto &cal : m_calibrations) + { + ResolutionInfos::const_iterator res(std::find_if(m_resolutions.cbegin() + RES_DESKTOP, m_resolutions.cend(), + [&](const RESOLUTION_INFO& info) { return StringUtils::EqualsNoCase(cal.strMode, info.strMode); })); + + if (res != m_resolutions.cend()) + { + //! @todo erase calibrations with default values + cal = *res; + } + } +} + +void CDisplaySettings::ClearCalibrations() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + m_calibrations.clear(); +} + +DisplayMode CDisplaySettings::GetCurrentDisplayMode() const +{ + if (GetCurrentResolution() == RES_WINDOW) + return DM_WINDOWED; + + return DM_FULLSCREEN; +} + +RESOLUTION CDisplaySettings::FindBestMatchingResolution(const std::map<RESOLUTION, RESOLUTION_INFO> &resolutionInfos, int width, int height, float refreshrate, unsigned flags) +{ + // find the closest match to these in our res vector. If we have the screen, we score the res + RESOLUTION bestRes = RES_DESKTOP; + float bestScore = FLT_MAX; + flags &= D3DPRESENTFLAG_MODEMASK; + + for (std::map<RESOLUTION, RESOLUTION_INFO>::const_iterator it = resolutionInfos.begin(); it != resolutionInfos.end(); ++it) + { + const RESOLUTION_INFO &info = it->second; + + if ((info.dwFlags & D3DPRESENTFLAG_MODEMASK) != flags) + continue; + + float score = 10 * (square_error((float)info.iScreenWidth, (float)width) + + square_error((float)info.iScreenHeight, (float)height) + + square_error(info.fRefreshRate, refreshrate)); + if (score < bestScore) + { + bestScore = score; + bestRes = it->first; + } + } + + return bestRes; +} + +RESOLUTION CDisplaySettings::GetResolutionFromString(const std::string &strResolution) +{ + + if (strResolution == "DESKTOP") + return RES_DESKTOP; + else if (strResolution == "WINDOW") + return RES_WINDOW; + else if (strResolution.size() >= 20) + { + // format: WWWWWHHHHHRRR.RRRRRP333, where W = width, H = height, R = refresh, P = interlace, 3 = stereo mode + int width = std::strtol(StringUtils::Mid(strResolution, 0,5).c_str(), NULL, 10); + int height = std::strtol(StringUtils::Mid(strResolution, 5,5).c_str(), NULL, 10); + float refresh = (float)std::strtod(StringUtils::Mid(strResolution, 10,9).c_str(), NULL); + unsigned flags = 0; + + // look for 'i' and treat everything else as progressive, + if(StringUtils::Mid(strResolution, 19,1) == "i") + flags |= D3DPRESENTFLAG_INTERLACED; + + if(StringUtils::Mid(strResolution, 20,3) == "sbs") + flags |= D3DPRESENTFLAG_MODE3DSBS; + else if(StringUtils::Mid(strResolution, 20,3) == "tab") + flags |= D3DPRESENTFLAG_MODE3DTB; + + std::map<RESOLUTION, RESOLUTION_INFO> resolutionInfos; + for (size_t resolution = RES_DESKTOP; resolution < CDisplaySettings::GetInstance().ResolutionInfoSize(); resolution++) + resolutionInfos.insert(std::make_pair((RESOLUTION)resolution, CDisplaySettings::GetInstance().GetResolutionInfo(resolution))); + + return FindBestMatchingResolution(resolutionInfos, width, height, refresh, flags); + } + + return RES_DESKTOP; +} + +std::string CDisplaySettings::GetStringFromResolution(RESOLUTION resolution, float refreshrate /* = 0.0f */) +{ + if (resolution == RES_WINDOW) + return "WINDOW"; + + if (resolution >= RES_DESKTOP && resolution < (RESOLUTION)CDisplaySettings::GetInstance().ResolutionInfoSize()) + { + const RESOLUTION_INFO &info = CDisplaySettings::GetInstance().GetResolutionInfo(resolution); + // also handle RES_DESKTOP resolutions with non-default refresh rates + if (resolution != RES_DESKTOP || (refreshrate > 0.0f && refreshrate != info.fRefreshRate)) + { + return StringUtils::Format("{:05}{:05}{:09.5f}{}", info.iScreenWidth, info.iScreenHeight, + refreshrate > 0.0f ? refreshrate : info.fRefreshRate, + ModeFlagsToString(info.dwFlags, true)); + } + } + + return "DESKTOP"; +} + +RESOLUTION CDisplaySettings::GetResolutionForScreen() +{ + DisplayMode mode = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_VIDEOSCREEN_SCREEN); + if (mode == DM_WINDOWED) + return RES_WINDOW; + + return RES_DESKTOP; +} + +static inline bool ModeSort(const StringSettingOption& i, const StringSettingOption& j) +{ + return (i.value > j.value); +} + +void CDisplaySettings::SettingOptionsModesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + for (auto index = (unsigned int)RES_CUSTOM; index < CDisplaySettings::GetInstance().ResolutionInfoSize(); ++index) + { + const auto mode = CDisplaySettings::GetInstance().GetResolutionInfo(index); + + if (mode.dwFlags ^ D3DPRESENTFLAG_INTERLACED) + { + auto setting = GetStringFromResolution((RESOLUTION)index, mode.fRefreshRate); + + list.emplace_back( + StringUtils::Format("{}x{}{} {:0.2f}Hz", mode.iScreenWidth, mode.iScreenHeight, + ModeFlagsToString(mode.dwFlags, false), mode.fRefreshRate), + setting); + } + } + + std::sort(list.begin(), list.end(), ModeSort); +} + +void CDisplaySettings::SettingOptionsRefreshChangeDelaysFiller( + const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + list.emplace_back(g_localizeStrings.Get(13551), 0); + + for (int i = 1; i <= MAX_REFRESH_CHANGE_DELAY; i++) + list.emplace_back( + StringUtils::Format(g_localizeStrings.Get(13553), static_cast<double>(i) / 10.0), i); +} + +void CDisplaySettings::SettingOptionsRefreshRatesFiller(const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + // get the proper resolution + RESOLUTION res = CDisplaySettings::GetInstance().GetDisplayResolution(); + if (res < RES_WINDOW) + return; + + // only add "Windowed" if in windowed mode + if (res == RES_WINDOW) + { + current = "WINDOW"; + list.emplace_back(g_localizeStrings.Get(242), current); + return; + } + + RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(res); + // The only meaningful parts of res here are iScreenWidth, iScreenHeight + std::vector<REFRESHRATE> refreshrates = CServiceBroker::GetWinSystem()->RefreshRates(resInfo.iScreenWidth, resInfo.iScreenHeight, resInfo.dwFlags); + + bool match = false; + for (std::vector<REFRESHRATE>::const_iterator refreshrate = refreshrates.begin(); refreshrate != refreshrates.end(); ++refreshrate) + { + std::string screenmode = GetStringFromResolution((RESOLUTION)refreshrate->ResInfo_Index, refreshrate->RefreshRate); + if (!match && StringUtils::EqualsNoCase(std::static_pointer_cast<const CSettingString>(setting)->GetValue(), screenmode)) + match = true; + list.emplace_back(StringUtils::Format("{:.2f}", refreshrate->RefreshRate), screenmode); + } + + if (!match) + current = GetStringFromResolution(res, CServiceBroker::GetWinSystem()->DefaultRefreshRate(refreshrates).RefreshRate); +} + +void CDisplaySettings::SettingOptionsResolutionsFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + RESOLUTION res = CDisplaySettings::GetInstance().GetDisplayResolution(); + RESOLUTION_INFO info = CDisplaySettings::GetInstance().GetResolutionInfo(res); + if (res == RES_WINDOW) + { + current = res; + list.emplace_back(g_localizeStrings.Get(242), res); + } + else + { + std::map<RESOLUTION, RESOLUTION_INFO> resolutionInfos; + std::vector<RESOLUTION_WHR> resolutions = CServiceBroker::GetWinSystem()->ScreenResolutions(info.fRefreshRate); + for (std::vector<RESOLUTION_WHR>::const_iterator resolution = resolutions.begin(); resolution != resolutions.end(); ++resolution) + { + list.emplace_back(StringUtils::Format("{}x{}{}", resolution->width, resolution->height, + ModeFlagsToString(resolution->flags, false)), + resolution->ResInfo_Index); + + resolutionInfos.insert(std::make_pair((RESOLUTION)resolution->ResInfo_Index, CDisplaySettings::GetInstance().GetResolutionInfo(resolution->ResInfo_Index))); + } + + current = FindBestMatchingResolution(resolutionInfos, + info.iScreenWidth, info.iScreenHeight, + info.fRefreshRate, info.dwFlags); + } +} + +void CDisplaySettings::SettingOptionsDispModeFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + // The user should only be able to disable windowed modes with the canwindowed + // setting. When the user sets canwindowed to true but the windowing system + // does not support windowed modes, we would just shoot ourselves in the foot + // by offering the option. + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_canWindowed && CServiceBroker::GetWinSystem()->CanDoWindowed()) + list.emplace_back(g_localizeStrings.Get(242), DM_WINDOWED); + + list.emplace_back(g_localizeStrings.Get(244), DM_FULLSCREEN); +} + +void CDisplaySettings::SettingOptionsStereoscopicModesFiller( + const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui != nullptr) + { + const CStereoscopicsManager &stereoscopicsManager = gui->GetStereoscopicsManager(); + + for (int i = RENDER_STEREO_MODE_OFF; i < RENDER_STEREO_MODE_COUNT; i++) + { + RENDER_STEREO_MODE mode = (RENDER_STEREO_MODE) i; + if (CServiceBroker::GetRenderSystem()->SupportsStereo(mode)) + list.emplace_back(stereoscopicsManager.GetLabelForStereoMode(mode), mode); + } + } +} + +void CDisplaySettings::SettingOptionsPreferredStereoscopicViewModesFiller( + const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + const CStereoscopicsManager &stereoscopicsManager = CServiceBroker::GetGUI()->GetStereoscopicsManager(); + + list.emplace_back(stereoscopicsManager.GetLabelForStereoMode(RENDER_STEREO_MODE_AUTO), + RENDER_STEREO_MODE_AUTO); // option for autodetect + // don't add "off" to the list of preferred modes as this doesn't make sense + for (int i = RENDER_STEREO_MODE_OFF +1; i < RENDER_STEREO_MODE_COUNT; i++) + { + RENDER_STEREO_MODE mode = (RENDER_STEREO_MODE) i; + // also skip "mono" mode which is no real stereoscopic mode + if (mode != RENDER_STEREO_MODE_MONO && CServiceBroker::GetRenderSystem()->SupportsStereo(mode)) + list.emplace_back(stereoscopicsManager.GetLabelForStereoMode(mode), mode); + } +} + +void CDisplaySettings::SettingOptionsMonitorsFiller(const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + auto winSystem = CServiceBroker::GetWinSystem(); + if (!winSystem) + return; + + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return; + + auto settings = settingsComponent->GetSettings(); + if (!settings) + return; + + const std::vector<std::string> monitors = winSystem->GetConnectedOutputs(); + std::string currentMonitor = settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR); + + bool foundMonitor = false; + for (auto const& monitor : monitors) + { + if(monitor == currentMonitor) + { + foundMonitor = true; + } + list.emplace_back(monitor, monitor); + } + + if (!foundMonitor && !current.empty()) + { + // Add current value so no monitor change is triggered when entering the settings screen and + // the preferred monitor is preserved + list.emplace_back(current, current); + } +} + +void CDisplaySettings::ClearCustomResolutions() +{ + if (m_resolutions.size() > RES_CUSTOM) + { + std::vector<RESOLUTION_INFO>::iterator firstCustom = m_resolutions.begin()+RES_CUSTOM; + m_resolutions.erase(firstCustom, m_resolutions.end()); + } +} + +void CDisplaySettings::SettingOptionsCmsModesFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + list.emplace_back(g_localizeStrings.Get(36580), CMS_MODE_3DLUT); +#ifdef HAVE_LCMS2 + list.emplace_back(g_localizeStrings.Get(36581), CMS_MODE_PROFILE); +#endif +} + +void CDisplaySettings::SettingOptionsCmsWhitepointsFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + list.emplace_back(g_localizeStrings.Get(36586), CMS_WHITEPOINT_D65); + list.emplace_back(g_localizeStrings.Get(36587), CMS_WHITEPOINT_D93); +} + +void CDisplaySettings::SettingOptionsCmsPrimariesFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + list.emplace_back(g_localizeStrings.Get(36588), CMS_PRIMARIES_AUTO); + list.emplace_back(g_localizeStrings.Get(36589), CMS_PRIMARIES_BT709); + list.emplace_back(g_localizeStrings.Get(36579), CMS_PRIMARIES_BT2020); + list.emplace_back(g_localizeStrings.Get(36590), CMS_PRIMARIES_170M); + list.emplace_back(g_localizeStrings.Get(36591), CMS_PRIMARIES_BT470M); + list.emplace_back(g_localizeStrings.Get(36592), CMS_PRIMARIES_BT470BG); + list.emplace_back(g_localizeStrings.Get(36593), CMS_PRIMARIES_240M); +} + +void CDisplaySettings::SettingOptionsCmsGammaModesFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + list.emplace_back(g_localizeStrings.Get(36582), CMS_TRC_BT1886); + list.emplace_back(g_localizeStrings.Get(36583), CMS_TRC_INPUT_OFFSET); + list.emplace_back(g_localizeStrings.Get(36584), CMS_TRC_OUTPUT_OFFSET); + list.emplace_back(g_localizeStrings.Get(36585), CMS_TRC_ABSOLUTE); +} + diff --git a/xbmc/settings/DisplaySettings.h b/xbmc/settings/DisplaySettings.h new file mode 100644 index 0000000..e02e80c --- /dev/null +++ b/xbmc/settings/DisplaySettings.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/ISubSettings.h" +#include "settings/lib/ISettingCallback.h" +#include "threads/CriticalSection.h" +#include "utils/Observer.h" +#include "windowing/Resolution.h" + +#include <map> +#include <set> +#include <utility> +#include <vector> + +class TiXmlNode; +struct IntegerSettingOption; +struct StringSettingOption; + +class CDisplaySettings : public ISettingCallback, public ISubSettings, + public Observable +{ +public: + static CDisplaySettings& GetInstance(); + + bool Load(const TiXmlNode *settings) override; + bool Save(TiXmlNode *settings) const override; + void Clear() override; + + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + bool OnSettingChanging(const std::shared_ptr<const CSetting>& setting) override; + bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) override; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + /*! + \brief Returns the currently active resolution + + This resolution might differ from the display resolution which is based on + the user's settings. + + \sa SetCurrentResolution + \sa GetResolutionInfo + \sa GetDisplayResolution + */ + RESOLUTION GetCurrentResolution() const { return m_currentResolution; } + void SetCurrentResolution(RESOLUTION resolution, bool save = false); + /*! + \brief Returns the best-matching resolution of the videoscreen.screenmode setting value + + This resolution might differ from the current resolution which is based on + the properties of the operating system and the attached displays. + + \sa GetCurrentResolution + */ + RESOLUTION GetDisplayResolution() const; + + const RESOLUTION_INFO& GetResolutionInfo(size_t index) const; + const RESOLUTION_INFO& GetResolutionInfo(RESOLUTION resolution) const; + RESOLUTION_INFO& GetResolutionInfo(size_t index); + RESOLUTION_INFO& GetResolutionInfo(RESOLUTION resolution); + size_t ResolutionInfoSize() const { return m_resolutions.size(); } + void AddResolutionInfo(const RESOLUTION_INFO &resolution); + + const RESOLUTION_INFO& GetCurrentResolutionInfo() const { return GetResolutionInfo(m_currentResolution); } + RESOLUTION_INFO& GetCurrentResolutionInfo() { return GetResolutionInfo(m_currentResolution); } + RESOLUTION GetResFromString(const std::string &strResolution) { return GetResolutionFromString(strResolution); } + std::string GetStringFromRes(const RESOLUTION resolution, float refreshrate = 0.0f) { return GetStringFromResolution(resolution, refreshrate); } + + void ApplyCalibrations(); + void UpdateCalibrations(); + void ClearCalibrations(); + void ClearCustomResolutions(); + + float GetZoomAmount() const { return m_zoomAmount; } + void SetZoomAmount(float zoomAmount) { m_zoomAmount = zoomAmount; } + float GetPixelRatio() const { return m_pixelRatio; } + void SetPixelRatio(float pixelRatio) { m_pixelRatio = pixelRatio; } + float GetVerticalShift() const { return m_verticalShift; } + void SetVerticalShift(float verticalShift) { m_verticalShift = verticalShift; } + bool IsNonLinearStretched() const { return m_nonLinearStretched; } + void SetNonLinearStretched(bool nonLinearStretch) { m_nonLinearStretched = nonLinearStretch; } + void SetMonitor(const std::string& monitor); + + static void SettingOptionsModesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + static void SettingOptionsRefreshChangeDelaysFiller( + const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsRefreshRatesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + static void SettingOptionsResolutionsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsDispModeFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsStereoscopicModesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsPreferredStereoscopicViewModesFiller( + const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsMonitorsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + static void SettingOptionsCmsModesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsCmsWhitepointsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsCmsPrimariesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + static void SettingOptionsCmsGammaModesFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + + +protected: + CDisplaySettings(); + CDisplaySettings(const CDisplaySettings&) = delete; + CDisplaySettings& operator=(CDisplaySettings const&) = delete; + ~CDisplaySettings() override; + + DisplayMode GetCurrentDisplayMode() const; + + static RESOLUTION GetResolutionFromString(const std::string &strResolution); + static std::string GetStringFromResolution(RESOLUTION resolution, float refreshrate = 0.0f); + static RESOLUTION GetResolutionForScreen(); + + static RESOLUTION FindBestMatchingResolution(const std::map<RESOLUTION, RESOLUTION_INFO> &resolutionInfos, int width, int height, float refreshrate, unsigned int flags); + +private: + // holds the real gui resolution + RESOLUTION m_currentResolution; + + typedef std::vector<RESOLUTION_INFO> ResolutionInfos; + ResolutionInfos m_resolutions; + ResolutionInfos m_calibrations; + + float m_zoomAmount; // current zoom amount + float m_pixelRatio; // current pixel ratio + float m_verticalShift; // current vertical shift + bool m_nonLinearStretched; // current non-linear stretch + + bool m_resolutionChangeAborted; + mutable CCriticalSection m_critical; +}; diff --git a/xbmc/settings/GameSettings.cpp b/xbmc/settings/GameSettings.cpp new file mode 100644 index 0000000..77d58ef --- /dev/null +++ b/xbmc/settings/GameSettings.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GameSettings.h" + +using namespace KODI; + +CGameSettings &CGameSettings::operator=(const CGameSettings &rhs) +{ + if (this != &rhs) + { + m_videoFilter = rhs.m_videoFilter; + m_stretchMode = rhs.m_stretchMode; + m_rotationDegCCW = rhs.m_rotationDegCCW; + } + return *this; +} + +void CGameSettings::Reset() +{ + m_videoFilter.clear(); + m_stretchMode = RETRO::STRETCHMODE::Normal; + m_rotationDegCCW = 0; +} + +bool CGameSettings::operator==(const CGameSettings &rhs) const +{ + return m_videoFilter == rhs.m_videoFilter && + m_stretchMode == rhs.m_stretchMode && + m_rotationDegCCW == rhs.m_rotationDegCCW; +} + +void CGameSettings::SetVideoFilter(const std::string &videoFilter) +{ + if (videoFilter != m_videoFilter) + { + m_videoFilter = videoFilter; + SetChanged(); + } +} + +void CGameSettings::SetStretchMode(RETRO::STRETCHMODE stretchMode) +{ + if (stretchMode != m_stretchMode) + { + m_stretchMode = stretchMode; + SetChanged(); + } +} + +void CGameSettings::SetRotationDegCCW(unsigned int rotation) +{ + if (rotation != m_rotationDegCCW) + { + m_rotationDegCCW = rotation; + SetChanged(); + } +} diff --git a/xbmc/settings/GameSettings.h b/xbmc/settings/GameSettings.h new file mode 100644 index 0000000..f4c6424 --- /dev/null +++ b/xbmc/settings/GameSettings.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "cores/GameSettings.h" +#include "utils/Observer.h" + +#include <string> + +class CGameSettings : public Observable +{ +public: + CGameSettings() { Reset(); } + CGameSettings(const CGameSettings &other) { *this = other; } + + CGameSettings &operator=(const CGameSettings &rhs); + + // Restore game settings to default + void Reset(); + + bool operator==(const CGameSettings &rhs) const; + bool operator!=(const CGameSettings &rhs) const { return !(*this == rhs); } + + const std::string &VideoFilter() const { return m_videoFilter; } + void SetVideoFilter(const std::string &videoFilter); + + KODI::RETRO::STRETCHMODE StretchMode() const { return m_stretchMode; } + void SetStretchMode(KODI::RETRO::STRETCHMODE stretchMode); + + unsigned int RotationDegCCW() const { return m_rotationDegCCW; } + void SetRotationDegCCW(unsigned int rotation); + +private: + // Video settings + std::string m_videoFilter; + KODI::RETRO::STRETCHMODE m_stretchMode; + unsigned int m_rotationDegCCW; +}; diff --git a/xbmc/settings/ISubSettings.h b/xbmc/settings/ISubSettings.h new file mode 100644 index 0000000..4cc2456 --- /dev/null +++ b/xbmc/settings/ISubSettings.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class TiXmlNode; + +/*! + \ingroup settings + \brief Interface defining methods to load additional setting values from an + XML file being loaded by the settings system. + */ +class ISubSettings +{ +public: + virtual ~ISubSettings() = default; + + /*! + \brief Load settings from the given XML node. + + \param settings XML node containing setting values + \return True if loading the settings was successful, false otherwise. + */ + virtual bool Load(const TiXmlNode *settings) { return true; } + /*! + \brief Save settings to the given XML node. + + \param settings XML node in which the settings will be saved + \return True if saving the settings was successful, false otherwise. + */ + virtual bool Save(TiXmlNode *settings) const { return true; } + /*! + \brief Clear any loaded setting values. + */ + virtual void Clear() { } +}; diff --git a/xbmc/settings/LibExportSettings.cpp b/xbmc/settings/LibExportSettings.cpp new file mode 100644 index 0000000..15be272 --- /dev/null +++ b/xbmc/settings/LibExportSettings.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +// LibExportSettings.cpp: implementation of the CLibExportSettings class. +// +////////////////////////////////////////////////////////////////////// + +#include "LibExportSettings.h" + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CLibExportSettings::CLibExportSettings() +{ + m_exporttype = ELIBEXPORT_SINGLEFILE; + m_itemstoexport = ELIBEXPORT_ALBUMS + ELIBEXPORT_ALBUMARTISTS; + m_overwrite = false; + m_artwork = false; + m_unscraped = false; + m_skipnfo = false; +} + +bool CLibExportSettings::operator!=(const CLibExportSettings &right) const +{ + if (m_exporttype != right.m_exporttype) + return true; + if (m_strPath != right.m_strPath) + return true; + if (m_overwrite != right.m_overwrite) + return true; + if (m_itemstoexport != right.m_itemstoexport) + return true; + + if (m_artwork != right.m_artwork) + return true; + if (m_unscraped != right.m_unscraped) + return true; + if (m_skipnfo != right.m_skipnfo) + return true; + + return false; +} + +bool CLibExportSettings::IsItemExported(ELIBEXPORTOPTIONS item) const +{ + return (m_itemstoexport & item); +} + +bool CLibExportSettings::IsArtists() const +{ + return (m_itemstoexport & ELIBEXPORT_ALBUMARTISTS) || + (m_itemstoexport & ELIBEXPORT_SONGARTISTS) || + (m_itemstoexport & ELIBEXPORT_OTHERARTISTS); +} + +std::vector<int> CLibExportSettings::GetExportItems() const +{ + std::vector<int> values; + if (IsItemExported(ELIBEXPORT_ALBUMS)) + values.emplace_back(ELIBEXPORT_ALBUMS); + if (IsItemExported(ELIBEXPORT_ALBUMARTISTS)) + values.emplace_back(ELIBEXPORT_ALBUMARTISTS); + if (IsItemExported(ELIBEXPORT_SONGARTISTS)) + values.emplace_back(ELIBEXPORT_SONGARTISTS); + if (IsItemExported(ELIBEXPORT_OTHERARTISTS)) + values.emplace_back(ELIBEXPORT_OTHERARTISTS); + if (IsItemExported(ELIBEXPORT_ACTORTHUMBS)) + values.emplace_back(ELIBEXPORT_ACTORTHUMBS); + if (IsItemExported(ELIBEXPORT_SONGS)) + values.emplace_back(ELIBEXPORT_SONGS); + return values; +} + +std::vector<int> CLibExportSettings::GetLimitedItems(int items) const +{ + std::vector<int> values; + if (IsItemExported(ELIBEXPORT_ALBUMS) && (items & ELIBEXPORT_ALBUMS)) + values.emplace_back(ELIBEXPORT_ALBUMS); + if (IsItemExported(ELIBEXPORT_ALBUMARTISTS) && (items & ELIBEXPORT_ALBUMARTISTS)) + values.emplace_back(ELIBEXPORT_ALBUMARTISTS); + if (IsItemExported(ELIBEXPORT_SONGARTISTS) && (items & ELIBEXPORT_SONGARTISTS)) + values.emplace_back(ELIBEXPORT_SONGARTISTS); + if (IsItemExported(ELIBEXPORT_OTHERARTISTS) && (items & ELIBEXPORT_OTHERARTISTS)) + values.emplace_back(ELIBEXPORT_OTHERARTISTS); + if (IsItemExported(ELIBEXPORT_ACTORTHUMBS) && (items & ELIBEXPORT_ACTORTHUMBS)) + values.emplace_back(ELIBEXPORT_ACTORTHUMBS); + if (IsItemExported(ELIBEXPORT_SONGS) && (items & ELIBEXPORT_SONGS)) + values.emplace_back(ELIBEXPORT_SONGS); + return values; +} + +bool CLibExportSettings::IsSingleFile() const +{ + return (m_exporttype == ELIBEXPORT_SINGLEFILE); +} + +bool CLibExportSettings::IsSeparateFiles() const +{ + return (m_exporttype == ELIBEXPORT_SEPARATEFILES); +} + +bool CLibExportSettings::IsToLibFolders() const +{ + return (m_exporttype == ELIBEXPORT_TOLIBRARYFOLDER); +} + +bool CLibExportSettings::IsArtistFoldersOnly() const +{ + return (m_exporttype == ELIBEXPORT_ARTISTFOLDERS); +} diff --git a/xbmc/settings/LibExportSettings.h b/xbmc/settings/LibExportSettings.h new file mode 100644 index 0000000..c7395fb --- /dev/null +++ b/xbmc/settings/LibExportSettings.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +// LibExportSettings.h: interface for the CLibExportSettings class. +// +////////////////////////////////////////////////////////////////////// + +#include "settings/lib/Setting.h" + +#include <string> + +// Enumeration of library export options (possibly OR'd together) +enum ELIBEXPORTOPTIONS +{ + ELIBEXPORT_SINGLEFILE = 0x0000, + ELIBEXPORT_SEPARATEFILES = 0x0001, + ELIBEXPORT_TOLIBRARYFOLDER = 0x0002, + ELIBEXPORT_OVERWRITE = 0x0004, + ELIBEXPORT_UNSCRAPED = 0x0008, + ELIBEXPORT_ALBUMS = 0x0010, + ELIBEXPORT_ALBUMARTISTS = 0x0020, + ELIBEXPORT_SONGARTISTS = 0x0040, + ELIBEXPORT_OTHERARTISTS = 0x0080, + ELIBEXPORT_ARTWORK = 0x0100, + ELIBEXPORT_NFOFILES = 0x0200, + ELIBEXPORT_ACTORTHUMBS = 0x0400, + ELIBEXPORT_ARTISTFOLDERS = 0x0800, + ELIBEXPORT_SONGS = 0x1000 +}; + +class CLibExportSettings +{ +public: + CLibExportSettings(); + ~CLibExportSettings() = default; + + bool operator!=(const CLibExportSettings &right) const; + bool IsItemExported(ELIBEXPORTOPTIONS item) const; + bool IsArtists() const; + std::vector<int> GetExportItems() const; + std::vector<int> GetLimitedItems(int items) const; + void ClearItems() { m_itemstoexport = 0; } + void AddItem(ELIBEXPORTOPTIONS item) { m_itemstoexport += item; } + unsigned int GetItemsToExport() { return m_itemstoexport; } + void SetItemsToExport(int itemstoexport) { m_itemstoexport = static_cast<unsigned int>(itemstoexport); } + unsigned int GetExportType() { return m_exporttype; } + void SetExportType(int exporttype) { m_exporttype = static_cast<unsigned int>(exporttype); } + bool IsSingleFile() const; + bool IsSeparateFiles() const; + bool IsToLibFolders() const; + bool IsArtistFoldersOnly() const; + + std::string m_strPath; + bool m_overwrite; + bool m_artwork; + bool m_unscraped; + bool m_skipnfo; +private: + unsigned int m_exporttype; //singlefile, separate files, to library folder + unsigned int m_itemstoexport; +}; diff --git a/xbmc/settings/MediaSettings.cpp b/xbmc/settings/MediaSettings.cpp new file mode 100644 index 0000000..09aad05 --- /dev/null +++ b/xbmc/settings/MediaSettings.cpp @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "MediaSettings.h" + +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "cores/RetroPlayer/RetroPlayerUtils.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "guilib/LocalizeStrings.h" +#include "interfaces/AnnouncementManager.h" +#include "interfaces/builtins/Builtins.h" +#include "messaging/helpers/DialogHelper.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicLibraryQueue.h" +#include "settings/Settings.h" +#include "settings/dialogs/GUIDialogLibExportSettings.h" +#include "settings/lib/Setting.h" +#include "storage/MediaManager.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoLibraryQueue.h" + +#include <limits.h> +#include <mutex> +#include <string> + +using namespace KODI; +using namespace KODI::MESSAGING; + +using KODI::MESSAGING::HELPERS::DialogResponse; + +CMediaSettings::CMediaSettings() +{ + m_watchedModes["files"] = WatchedModeAll; + m_watchedModes["movies"] = WatchedModeAll; + m_watchedModes["tvshows"] = WatchedModeAll; + m_watchedModes["musicvideos"] = WatchedModeAll; + m_watchedModes["recordings"] = WatchedModeAll; + + m_musicPlaylistRepeat = false; + m_musicPlaylistShuffle = false; + m_videoPlaylistRepeat = false; + m_videoPlaylistShuffle = false; + + m_mediaStartWindowed = false; + m_additionalSubtitleDirectoryChecked = 0; + + m_musicNeedsUpdate = 0; + m_videoNeedsUpdate = 0; +} + +CMediaSettings::~CMediaSettings() = default; + +CMediaSettings& CMediaSettings::GetInstance() +{ + static CMediaSettings sMediaSettings; + return sMediaSettings; +} + +bool CMediaSettings::Load(const TiXmlNode *settings) +{ + if (settings == NULL) + return false; + + std::unique_lock<CCriticalSection> lock(m_critical); + const TiXmlElement *pElement = settings->FirstChildElement("defaultvideosettings"); + if (pElement != NULL) + { + int interlaceMethod; + XMLUtils::GetInt(pElement, "interlacemethod", interlaceMethod, VS_INTERLACEMETHOD_NONE, VS_INTERLACEMETHOD_MAX); + + m_defaultVideoSettings.m_InterlaceMethod = (EINTERLACEMETHOD)interlaceMethod; + int scalingMethod; + if (!XMLUtils::GetInt(pElement, "scalingmethod", scalingMethod, VS_SCALINGMETHOD_NEAREST, VS_SCALINGMETHOD_MAX)) + scalingMethod = (int)VS_SCALINGMETHOD_LINEAR; + m_defaultVideoSettings.m_ScalingMethod = (ESCALINGMETHOD)scalingMethod; + + XMLUtils::GetInt(pElement, "viewmode", m_defaultVideoSettings.m_ViewMode, ViewModeNormal, ViewModeZoom110Width); + if (!XMLUtils::GetFloat(pElement, "zoomamount", m_defaultVideoSettings.m_CustomZoomAmount, 0.5f, 2.0f)) + m_defaultVideoSettings.m_CustomZoomAmount = 1.0f; + if (!XMLUtils::GetFloat(pElement, "pixelratio", m_defaultVideoSettings.m_CustomPixelRatio, 0.5f, 2.0f)) + m_defaultVideoSettings.m_CustomPixelRatio = 1.0f; + if (!XMLUtils::GetFloat(pElement, "verticalshift", m_defaultVideoSettings.m_CustomVerticalShift, -2.0f, 2.0f)) + m_defaultVideoSettings.m_CustomVerticalShift = 0.0f; + if (!XMLUtils::GetFloat(pElement, "volumeamplification", m_defaultVideoSettings.m_VolumeAmplification, VOLUME_DRC_MINIMUM * 0.01f, VOLUME_DRC_MAXIMUM * 0.01f)) + m_defaultVideoSettings.m_VolumeAmplification = VOLUME_DRC_MINIMUM * 0.01f; + if (!XMLUtils::GetFloat(pElement, "noisereduction", m_defaultVideoSettings.m_NoiseReduction, 0.0f, 1.0f)) + m_defaultVideoSettings.m_NoiseReduction = 0.0f; + XMLUtils::GetBoolean(pElement, "postprocess", m_defaultVideoSettings.m_PostProcess); + if (!XMLUtils::GetFloat(pElement, "sharpness", m_defaultVideoSettings.m_Sharpness, -1.0f, 1.0f)) + m_defaultVideoSettings.m_Sharpness = 0.0f; + XMLUtils::GetBoolean(pElement, "showsubtitles", m_defaultVideoSettings.m_SubtitleOn); + if (!XMLUtils::GetFloat(pElement, "brightness", m_defaultVideoSettings.m_Brightness, 0, 100)) + m_defaultVideoSettings.m_Brightness = 50; + if (!XMLUtils::GetFloat(pElement, "contrast", m_defaultVideoSettings.m_Contrast, 0, 100)) + m_defaultVideoSettings.m_Contrast = 50; + if (!XMLUtils::GetFloat(pElement, "gamma", m_defaultVideoSettings.m_Gamma, 0, 100)) + m_defaultVideoSettings.m_Gamma = 20; + if (!XMLUtils::GetFloat(pElement, "audiodelay", m_defaultVideoSettings.m_AudioDelay, -10.0f, 10.0f)) + m_defaultVideoSettings.m_AudioDelay = 0.0f; + if (!XMLUtils::GetFloat(pElement, "subtitledelay", m_defaultVideoSettings.m_SubtitleDelay, -10.0f, 10.0f)) + m_defaultVideoSettings.m_SubtitleDelay = 0.0f; + XMLUtils::GetBoolean(pElement, "nonlinstretch", m_defaultVideoSettings.m_CustomNonLinStretch); + if (!XMLUtils::GetInt(pElement, "stereomode", m_defaultVideoSettings.m_StereoMode)) + m_defaultVideoSettings.m_StereoMode = 0; + if (!XMLUtils::GetInt(pElement, "centermixlevel", m_defaultVideoSettings.m_CenterMixLevel)) + m_defaultVideoSettings.m_CenterMixLevel = 0; + + int toneMapMethod; + if (!XMLUtils::GetInt(pElement, "tonemapmethod", toneMapMethod, VS_TONEMAPMETHOD_OFF, + VS_TONEMAPMETHOD_MAX)) + toneMapMethod = VS_TONEMAPMETHOD_REINHARD; + m_defaultVideoSettings.m_ToneMapMethod = static_cast<ETONEMAPMETHOD>(toneMapMethod); + + if (!XMLUtils::GetFloat(pElement, "tonemapparam", m_defaultVideoSettings.m_ToneMapParam, 0.1f, 5.0f)) + m_defaultVideoSettings.m_ToneMapParam = 1.0f; + } + + m_defaultGameSettings.Reset(); + pElement = settings->FirstChildElement("defaultgamesettings"); + if (pElement != nullptr) + { + std::string videoFilter; + if (XMLUtils::GetString(pElement, "videofilter", videoFilter)) + m_defaultGameSettings.SetVideoFilter(videoFilter); + + std::string stretchMode; + if (XMLUtils::GetString(pElement, "stretchmode", stretchMode)) + { + RETRO::STRETCHMODE sm = RETRO::CRetroPlayerUtils::IdentifierToStretchMode(stretchMode); + m_defaultGameSettings.SetStretchMode(sm); + } + + int rotation; + if (XMLUtils::GetInt(pElement, "rotation", rotation, 0, 270) && rotation >= 0) + m_defaultGameSettings.SetRotationDegCCW(static_cast<unsigned int>(rotation)); + } + + // mymusic settings + pElement = settings->FirstChildElement("mymusic"); + if (pElement != NULL) + { + const TiXmlElement *pChild = pElement->FirstChildElement("playlist"); + if (pChild != NULL) + { + XMLUtils::GetBoolean(pChild, "repeat", m_musicPlaylistRepeat); + XMLUtils::GetBoolean(pChild, "shuffle", m_musicPlaylistShuffle); + } + if (!XMLUtils::GetInt(pElement, "needsupdate", m_musicNeedsUpdate, 0, INT_MAX)) + m_musicNeedsUpdate = 0; + } + + // Set music playlist player repeat and shuffle from loaded settings + if (m_musicPlaylistRepeat) + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_MUSIC, PLAYLIST::RepeatState::ALL); + else + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_MUSIC, + PLAYLIST::RepeatState::NONE); + CServiceBroker::GetPlaylistPlayer().SetShuffle(PLAYLIST::TYPE_MUSIC, m_musicPlaylistShuffle); + + // Read the watchmode settings for the various media views + pElement = settings->FirstChildElement("myvideos"); + if (pElement != NULL) + { + int tmp; + if (XMLUtils::GetInt(pElement, "watchmodemovies", tmp, (int)WatchedModeAll, (int)WatchedModeWatched)) + m_watchedModes["movies"] = (WatchedMode)tmp; + if (XMLUtils::GetInt(pElement, "watchmodetvshows", tmp, (int)WatchedModeAll, (int)WatchedModeWatched)) + m_watchedModes["tvshows"] = (WatchedMode)tmp; + if (XMLUtils::GetInt(pElement, "watchmodemusicvideos", tmp, (int)WatchedModeAll, (int)WatchedModeWatched)) + m_watchedModes["musicvideos"] = (WatchedMode)tmp; + if (XMLUtils::GetInt(pElement, "watchmoderecordings", tmp, static_cast<int>(WatchedModeAll), static_cast<int>(WatchedModeWatched))) + m_watchedModes["recordings"] = static_cast<WatchedMode>(tmp); + + const TiXmlElement *pChild = pElement->FirstChildElement("playlist"); + if (pChild != NULL) + { + XMLUtils::GetBoolean(pChild, "repeat", m_videoPlaylistRepeat); + XMLUtils::GetBoolean(pChild, "shuffle", m_videoPlaylistShuffle); + } + if (!XMLUtils::GetInt(pElement, "needsupdate", m_videoNeedsUpdate, 0, INT_MAX)) + m_videoNeedsUpdate = 0; + } + + // Set video playlist player repeat and shuffle from loaded settings + if (m_videoPlaylistRepeat) + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_VIDEO, PLAYLIST::RepeatState::ALL); + else + CServiceBroker::GetPlaylistPlayer().SetRepeat(PLAYLIST::TYPE_VIDEO, + PLAYLIST::RepeatState::NONE); + CServiceBroker::GetPlaylistPlayer().SetShuffle(PLAYLIST::TYPE_VIDEO, m_videoPlaylistShuffle); + + return true; +} + +bool CMediaSettings::Save(TiXmlNode *settings) const +{ + if (settings == NULL) + return false; + + std::unique_lock<CCriticalSection> lock(m_critical); + // default video settings + TiXmlElement videoSettingsNode("defaultvideosettings"); + TiXmlNode *pNode = settings->InsertEndChild(videoSettingsNode); + if (pNode == NULL) + return false; + + XMLUtils::SetInt(pNode, "interlacemethod", m_defaultVideoSettings.m_InterlaceMethod); + XMLUtils::SetInt(pNode, "scalingmethod", m_defaultVideoSettings.m_ScalingMethod); + XMLUtils::SetFloat(pNode, "noisereduction", m_defaultVideoSettings.m_NoiseReduction); + XMLUtils::SetBoolean(pNode, "postprocess", m_defaultVideoSettings.m_PostProcess); + XMLUtils::SetFloat(pNode, "sharpness", m_defaultVideoSettings.m_Sharpness); + XMLUtils::SetInt(pNode, "viewmode", m_defaultVideoSettings.m_ViewMode); + XMLUtils::SetFloat(pNode, "zoomamount", m_defaultVideoSettings.m_CustomZoomAmount); + XMLUtils::SetFloat(pNode, "pixelratio", m_defaultVideoSettings.m_CustomPixelRatio); + XMLUtils::SetFloat(pNode, "verticalshift", m_defaultVideoSettings.m_CustomVerticalShift); + XMLUtils::SetFloat(pNode, "volumeamplification", m_defaultVideoSettings.m_VolumeAmplification); + XMLUtils::SetBoolean(pNode, "showsubtitles", m_defaultVideoSettings.m_SubtitleOn); + XMLUtils::SetFloat(pNode, "brightness", m_defaultVideoSettings.m_Brightness); + XMLUtils::SetFloat(pNode, "contrast", m_defaultVideoSettings.m_Contrast); + XMLUtils::SetFloat(pNode, "gamma", m_defaultVideoSettings.m_Gamma); + XMLUtils::SetFloat(pNode, "audiodelay", m_defaultVideoSettings.m_AudioDelay); + XMLUtils::SetFloat(pNode, "subtitledelay", m_defaultVideoSettings.m_SubtitleDelay); + XMLUtils::SetBoolean(pNode, "nonlinstretch", m_defaultVideoSettings.m_CustomNonLinStretch); + XMLUtils::SetInt(pNode, "stereomode", m_defaultVideoSettings.m_StereoMode); + XMLUtils::SetInt(pNode, "centermixlevel", m_defaultVideoSettings.m_CenterMixLevel); + XMLUtils::SetInt(pNode, "tonemapmethod", m_defaultVideoSettings.m_ToneMapMethod); + XMLUtils::SetFloat(pNode, "tonemapparam", m_defaultVideoSettings.m_ToneMapParam); + + // default audio settings for dsp addons + TiXmlElement audioSettingsNode("defaultaudiosettings"); + pNode = settings->InsertEndChild(audioSettingsNode); + if (pNode == NULL) + return false; + + // Default game settings + TiXmlElement gameSettingsNode("defaultgamesettings"); + pNode = settings->InsertEndChild(gameSettingsNode); + if (pNode == nullptr) + return false; + + XMLUtils::SetString(pNode, "videofilter", m_defaultGameSettings.VideoFilter()); + std::string sm = RETRO::CRetroPlayerUtils::StretchModeToIdentifier(m_defaultGameSettings.StretchMode()); + XMLUtils::SetString(pNode, "stretchmode", sm); + XMLUtils::SetInt(pNode, "rotation", m_defaultGameSettings.RotationDegCCW()); + + // mymusic + pNode = settings->FirstChild("mymusic"); + if (pNode == NULL) + { + TiXmlElement videosNode("mymusic"); + pNode = settings->InsertEndChild(videosNode); + if (pNode == NULL) + return false; + } + + TiXmlElement musicPlaylistNode("playlist"); + TiXmlNode *playlistNode = pNode->InsertEndChild(musicPlaylistNode); + if (playlistNode == NULL) + return false; + XMLUtils::SetBoolean(playlistNode, "repeat", m_musicPlaylistRepeat); + XMLUtils::SetBoolean(playlistNode, "shuffle", m_musicPlaylistShuffle); + + XMLUtils::SetInt(pNode, "needsupdate", m_musicNeedsUpdate); + + // myvideos + pNode = settings->FirstChild("myvideos"); + if (pNode == NULL) + { + TiXmlElement videosNode("myvideos"); + pNode = settings->InsertEndChild(videosNode); + if (pNode == NULL) + return false; + } + + XMLUtils::SetInt(pNode, "watchmodemovies", m_watchedModes.find("movies")->second); + XMLUtils::SetInt(pNode, "watchmodetvshows", m_watchedModes.find("tvshows")->second); + XMLUtils::SetInt(pNode, "watchmodemusicvideos", m_watchedModes.find("musicvideos")->second); + XMLUtils::SetInt(pNode, "watchmoderecordings", m_watchedModes.find("recordings")->second); + + TiXmlElement videoPlaylistNode("playlist"); + playlistNode = pNode->InsertEndChild(videoPlaylistNode); + if (playlistNode == NULL) + return false; + XMLUtils::SetBoolean(playlistNode, "repeat", m_videoPlaylistRepeat); + XMLUtils::SetBoolean(playlistNode, "shuffle", m_videoPlaylistShuffle); + + XMLUtils::SetInt(pNode, "needsupdate", m_videoNeedsUpdate); + + return true; +} + +void CMediaSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + const std::string &settingId = setting->GetId(); + if (settingId == CSettings::SETTING_MUSICLIBRARY_CLEANUP) + { + if (HELPERS::ShowYesNoDialogText(CVariant{313}, CVariant{333}) == DialogResponse::CHOICE_YES) + { + if (CMusicLibraryQueue::GetInstance().IsRunning()) + HELPERS::ShowOKDialogText(CVariant{700}, CVariant{703}); + else + CMusicLibraryQueue::GetInstance().CleanLibrary(true); + } + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT) + { + CLibExportSettings m_musicExportSettings; + if (CGUIDialogLibExportSettings::Show(m_musicExportSettings)) + { + // Export music library showing progress dialog + CMusicLibraryQueue::GetInstance().ExportLibrary(m_musicExportSettings, true); + } + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_IMPORT) + { + std::string path; + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + CServiceBroker::GetMediaManager().GetNetworkLocations(shares); + CServiceBroker::GetMediaManager().GetRemovableDrives(shares); + + if (CGUIDialogFileBrowser::ShowAndGetFile(shares, "musicdb.xml", g_localizeStrings.Get(651) , path)) + { + // Import data to music library showing progress dialog + CMusicLibraryQueue::GetInstance().ImportLibrary(path, true); + } + } + else if (settingId == CSettings::SETTING_VIDEOLIBRARY_CLEANUP) + { + if (HELPERS::ShowYesNoDialogText(CVariant{313}, CVariant{333}) == DialogResponse::CHOICE_YES) + { + if (!CVideoLibraryQueue::GetInstance().CleanLibraryModal()) + HELPERS::ShowOKDialogText(CVariant{700}, CVariant{703}); + } + } + else if (settingId == CSettings::SETTING_VIDEOLIBRARY_EXPORT) + CBuiltins::GetInstance().Execute("exportlibrary(video)"); + else if (settingId == CSettings::SETTING_VIDEOLIBRARY_IMPORT) + { + std::string path; + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + CServiceBroker::GetMediaManager().GetNetworkLocations(shares); + CServiceBroker::GetMediaManager().GetRemovableDrives(shares); + + if (CGUIDialogFileBrowser::ShowAndGetDirectory(shares, g_localizeStrings.Get(651) , path)) + { + CVideoDatabase videodatabase; + videodatabase.Open(); + videodatabase.ImportFromXML(path); + videodatabase.Close(); + } + } +} + +void CMediaSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + if (setting->GetId() == CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS) + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnRefresh"); +} + +int CMediaSettings::GetWatchedMode(const std::string &content) const +{ + std::unique_lock<CCriticalSection> lock(m_critical); + WatchedModes::const_iterator it = m_watchedModes.find(GetWatchedContent(content)); + if (it != m_watchedModes.end()) + return it->second; + + return WatchedModeAll; +} + +void CMediaSettings::SetWatchedMode(const std::string &content, WatchedMode mode) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + WatchedModes::iterator it = m_watchedModes.find(GetWatchedContent(content)); + if (it != m_watchedModes.end()) + it->second = mode; +} + +void CMediaSettings::CycleWatchedMode(const std::string &content) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + WatchedModes::iterator it = m_watchedModes.find(GetWatchedContent(content)); + if (it != m_watchedModes.end()) + { + it->second = (WatchedMode)((int)it->second + 1); + if (it->second > WatchedModeWatched) + it->second = WatchedModeAll; + } +} + +std::string CMediaSettings::GetWatchedContent(const std::string &content) +{ + if (content == "seasons" || content == "episodes") + return "tvshows"; + + return content; +} diff --git a/xbmc/settings/MediaSettings.h b/xbmc/settings/MediaSettings.h new file mode 100644 index 0000000..d239234 --- /dev/null +++ b/xbmc/settings/MediaSettings.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "cores/VideoSettings.h" +#include "settings/GameSettings.h" +#include "settings/ISubSettings.h" +#include "settings/LibExportSettings.h" +#include "settings/lib/ISettingCallback.h" +#include "settings/lib/ISettingsHandler.h" +#include "threads/CriticalSection.h" + +#include <map> +#include <string> + +#define VOLUME_DRC_MINIMUM 0 // 0dB +#define VOLUME_DRC_MAXIMUM 6000 // 60dB + +class TiXmlNode; + +// Step used to increase/decrease audio delay +static constexpr float AUDIO_DELAY_STEP = 0.025f; + +typedef enum { + WatchedModeAll = 0, + WatchedModeUnwatched, + WatchedModeWatched +} WatchedMode; + +class CMediaSettings : public ISettingCallback, public ISettingsHandler, public ISubSettings +{ +public: + static CMediaSettings& GetInstance(); + + bool Load(const TiXmlNode *settings) override; + bool Save(TiXmlNode *settings) const override; + + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + const CVideoSettings& GetDefaultVideoSettings() const { return m_defaultVideoSettings; } + CVideoSettings& GetDefaultVideoSettings() { return m_defaultVideoSettings; } + + const CGameSettings& GetDefaultGameSettings() const { return m_defaultGameSettings; } + CGameSettings& GetDefaultGameSettings() { return m_defaultGameSettings; } + const CGameSettings& GetCurrentGameSettings() const { return m_currentGameSettings; } + CGameSettings& GetCurrentGameSettings() { return m_currentGameSettings; } + + /*! \brief Retrieve the watched mode for the given content type + \param content Current content type + \return the current watch mode for this content type, WATCH_MODE_ALL if the content type is unknown. + \sa SetWatchMode + */ + int GetWatchedMode(const std::string &content) const; + + /*! \brief Set the watched mode for the given content type + \param content Current content type + \param value Watched mode to set + \sa GetWatchMode + */ + void SetWatchedMode(const std::string &content, WatchedMode mode); + + /*! \brief Cycle the watched mode for the given content type + \param content Current content type + \sa GetWatchMode, SetWatchMode + */ + void CycleWatchedMode(const std::string &content); + + void SetMusicPlaylistRepeat(bool repeats) { m_musicPlaylistRepeat = repeats; } + void SetMusicPlaylistShuffled(bool shuffled) { m_musicPlaylistShuffle = shuffled; } + + void SetVideoPlaylistRepeat(bool repeats) { m_videoPlaylistRepeat = repeats; } + void SetVideoPlaylistShuffled(bool shuffled) { m_videoPlaylistShuffle = shuffled; } + + bool DoesMediaStartWindowed() const { return m_mediaStartWindowed; } + void SetMediaStartWindowed(bool windowed) { m_mediaStartWindowed = windowed; } + int GetAdditionalSubtitleDirectoryChecked() const { return m_additionalSubtitleDirectoryChecked; } + void SetAdditionalSubtitleDirectoryChecked(int checked) { m_additionalSubtitleDirectoryChecked = checked; } + + int GetMusicNeedsUpdate() const { return m_musicNeedsUpdate; } + void SetMusicNeedsUpdate(int version) { m_musicNeedsUpdate = version; } + int GetVideoNeedsUpdate() const { return m_videoNeedsUpdate; } + void SetVideoNeedsUpdate(int version) { m_videoNeedsUpdate = version; } + +protected: + CMediaSettings(); + CMediaSettings(const CMediaSettings&) = delete; + CMediaSettings& operator=(CMediaSettings const&) = delete; + ~CMediaSettings() override; + + static std::string GetWatchedContent(const std::string &content); + +private: + CVideoSettings m_defaultVideoSettings; + + CGameSettings m_defaultGameSettings; + CGameSettings m_currentGameSettings; + + typedef std::map<std::string, WatchedMode> WatchedModes; + WatchedModes m_watchedModes; + + bool m_musicPlaylistRepeat; + bool m_musicPlaylistShuffle; + bool m_videoPlaylistRepeat; + bool m_videoPlaylistShuffle; + + bool m_mediaStartWindowed; + int m_additionalSubtitleDirectoryChecked; + + int m_musicNeedsUpdate; ///< if a database update means an update is required (set to the version number of the db) + int m_videoNeedsUpdate; ///< if a database update means an update is required (set to the version number of the db) + + mutable CCriticalSection m_critical; +}; diff --git a/xbmc/settings/MediaSourceSettings.cpp b/xbmc/settings/MediaSourceSettings.cpp new file mode 100644 index 0000000..183131f --- /dev/null +++ b/xbmc/settings/MediaSourceSettings.cpp @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2013-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "MediaSourceSettings.h" + +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "media/MediaLockState.h" +#include "network/WakeOnAccess.h" +#include "profiles/ProfileManager.h" +#include "settings/SettingsComponent.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <cstdlib> +#include <string> + +#define SOURCES_FILE "sources.xml" +#define XML_SOURCES "sources" +#define XML_SOURCE "source" + +CMediaSourceSettings::CMediaSourceSettings() +{ + Clear(); +} + +CMediaSourceSettings::~CMediaSourceSettings() = default; + +CMediaSourceSettings& CMediaSourceSettings::GetInstance() +{ + static CMediaSourceSettings sMediaSourceSettings; + return sMediaSourceSettings; +} + +std::string CMediaSourceSettings::GetSourcesFile() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::string file; + if (profileManager->GetCurrentProfile().hasSources()) + file = profileManager->GetProfileUserDataFolder(); + else + file = profileManager->GetUserDataFolder(); + + return URIUtils::AddFileToFolder(file, SOURCES_FILE); +} + +void CMediaSourceSettings::OnSettingsLoaded() +{ + Load(); +} + +void CMediaSourceSettings::OnSettingsUnloaded() +{ + Clear(); +} + +bool CMediaSourceSettings::Load() +{ + return Load(GetSourcesFile()); +} + +bool CMediaSourceSettings::Load(const std::string &file) +{ + Clear(); + + if (!CFileUtils::Exists(file)) + return false; + + CLog::Log(LOGINFO, "CMediaSourceSettings: loading media sources from {}", file); + + // load xml file + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile(file)) + { + CLog::Log(LOGERROR, "CMediaSourceSettings: error loading {}: Line {}, {}", file, + xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); + return false; + } + + TiXmlElement *pRootElement = xmlDoc.RootElement(); + if (pRootElement == NULL || !StringUtils::EqualsNoCase(pRootElement->ValueStr(), XML_SOURCES)) + CLog::Log(LOGERROR, "CMediaSourceSettings: sources.xml file does not contain <sources>"); + + // parse sources + std::string dummy; + GetSources(pRootElement, "video", m_videoSources, dummy); + GetSources(pRootElement, "programs", m_programSources, m_defaultProgramSource); + GetSources(pRootElement, "pictures", m_pictureSources, m_defaultPictureSource); + GetSources(pRootElement, "files", m_fileSources, m_defaultFileSource); + GetSources(pRootElement, "music", m_musicSources, m_defaultMusicSource); + GetSources(pRootElement, "games", m_gameSources, dummy); + + return true; +} + +bool CMediaSourceSettings::Save() +{ + return Save(GetSourcesFile()); +} + +bool CMediaSourceSettings::Save(const std::string &file) const +{ + //! @todo Should we be specifying utf8 here?? + CXBMCTinyXML doc; + TiXmlElement xmlRootElement(XML_SOURCES); + TiXmlNode *pRoot = doc.InsertEndChild(xmlRootElement); + if (pRoot == NULL) + return false; + + // ok, now run through and save each sources section + SetSources(pRoot, "programs", m_programSources, m_defaultProgramSource); + SetSources(pRoot, "video", m_videoSources, ""); + SetSources(pRoot, "music", m_musicSources, m_defaultMusicSource); + SetSources(pRoot, "pictures", m_pictureSources, m_defaultPictureSource); + SetSources(pRoot, "files", m_fileSources, m_defaultFileSource); + SetSources(pRoot, "games", m_gameSources, ""); + + CWakeOnAccess::GetInstance().QueueMACDiscoveryForAllRemotes(); + + return doc.SaveFile(file); +} + +void CMediaSourceSettings::Clear() +{ + m_programSources.clear(); + m_pictureSources.clear(); + m_fileSources.clear(); + m_musicSources.clear(); + m_videoSources.clear(); + m_gameSources.clear(); +} + +VECSOURCES* CMediaSourceSettings::GetSources(const std::string &type) +{ + if (type == "programs" || type == "myprograms") + return &m_programSources; + else if (type == "files") + return &m_fileSources; + else if (type == "music") + return &m_musicSources; + else if (type == "video" || type == "videos") + return &m_videoSources; + else if (type == "pictures") + return &m_pictureSources; + else if (type == "games") + return &m_gameSources; + + return NULL; +} + +const std::string& CMediaSourceSettings::GetDefaultSource(const std::string &type) const +{ + if (type == "programs" || type == "myprograms") + return m_defaultProgramSource; + else if (type == "files") + return m_defaultFileSource; + else if (type == "music") + return m_defaultMusicSource; + else if (type == "pictures") + return m_defaultPictureSource; + + return StringUtils::Empty; +} + +void CMediaSourceSettings::SetDefaultSource(const std::string &type, const std::string &source) +{ + if (type == "programs" || type == "myprograms") + m_defaultProgramSource = source; + else if (type == "files") + m_defaultFileSource = source; + else if (type == "music") + m_defaultMusicSource = source; + else if (type == "pictures") + m_defaultPictureSource = source; +} + +// NOTE: This function does NOT save the sources.xml file - you need to call SaveSources() separately. +bool CMediaSourceSettings::UpdateSource(const std::string &strType, const std::string &strOldName, const std::string &strUpdateChild, const std::string &strUpdateValue) +{ + VECSOURCES *pShares = GetSources(strType); + if (pShares == NULL) + return false; + + for (IVECSOURCES it = pShares->begin(); it != pShares->end(); it++) + { + if (it->strName == strOldName) + { + if (strUpdateChild == "name") + it->strName = strUpdateValue; + else if (strUpdateChild == "lockmode") + it->m_iLockMode = (LockType)std::strtol(strUpdateValue.c_str(), NULL, 10); + else if (strUpdateChild == "lockcode") + it->m_strLockCode = strUpdateValue; + else if (strUpdateChild == "badpwdcount") + it->m_iBadPwdCount = (int)std::strtol(strUpdateValue.c_str(), NULL, 10); + else if (strUpdateChild == "thumbnail") + it->m_strThumbnailImage = strUpdateValue; + else if (strUpdateChild == "path") + { + it->vecPaths.clear(); + it->strPath = strUpdateValue; + it->vecPaths.push_back(strUpdateValue); + } + else + return false; + + return true; + } + } + + return false; +} + +bool CMediaSourceSettings::DeleteSource(const std::string &strType, const std::string &strName, const std::string &strPath, bool virtualSource /* = false */) +{ + VECSOURCES *pShares = GetSources(strType); + if (pShares == NULL) + return false; + + bool found = false; + + for (IVECSOURCES it = pShares->begin(); it != pShares->end(); it++) + { + if (it->strName == strName && it->strPath == strPath) + { + CLog::Log(LOGDEBUG, "CMediaSourceSettings: found share, removing!"); + pShares->erase(it); + found = true; + break; + } + } + + if (virtualSource) + return found; + + return Save(); +} + +bool CMediaSourceSettings::AddShare(const std::string &type, const CMediaSource &share) +{ + VECSOURCES *pShares = GetSources(type); + if (pShares == NULL) + return false; + + // translate dir and add to our current shares + std::string strPath1 = share.strPath; + if (strPath1.empty()) + { + CLog::Log(LOGERROR, "CMediaSourceSettings: unable to add empty path"); + return false; + } + StringUtils::ToUpper(strPath1); + + CMediaSource shareToAdd = share; + if (strPath1.at(0) == '$') + { + shareToAdd.strPath = CUtil::TranslateSpecialSource(strPath1); + if (!share.strPath.empty()) + CLog::Log(LOGDEBUG, "CMediaSourceSettings: translated ({}) to path ({})", strPath1, + shareToAdd.strPath); + else + { + CLog::Log(LOGDEBUG, "CMediaSourceSettings: skipping invalid special directory token ({})", + strPath1); + return false; + } + } + pShares->push_back(shareToAdd); + + if (!share.m_ignore) + return Save(); + + return true; +} + +bool CMediaSourceSettings::UpdateShare(const std::string &type, const std::string &oldName, const CMediaSource &share) +{ + VECSOURCES *pShares = GetSources(type); + if (pShares == NULL) + return false; + + // update our current share list + CMediaSource* pShare = NULL; + for (IVECSOURCES it = pShares->begin(); it != pShares->end(); it++) + { + if (it->strName == oldName) + { + it->strName = share.strName; + it->strPath = share.strPath; + it->vecPaths = share.vecPaths; + pShare = &(*it); + break; + } + } + + if (pShare == NULL) + return false; + + // Update our XML file as well + return Save(); +} + +bool CMediaSourceSettings::GetSource(const std::string &category, const TiXmlNode *source, CMediaSource &share) +{ + const TiXmlNode *pNodeName = source->FirstChild("name"); + std::string strName; + if (pNodeName && pNodeName->FirstChild()) + strName = pNodeName->FirstChild()->ValueStr(); + + // get multiple paths + std::vector<std::string> vecPaths; + const TiXmlElement *pPathName = source->FirstChildElement("path"); + while (pPathName != NULL) + { + if (pPathName->FirstChild()) + { + std::string strPath = pPathName->FirstChild()->ValueStr(); + + // make sure there are no virtualpaths or stack paths defined in sources.xml + if (!URIUtils::IsStack(strPath)) + { + // translate special tags + if (!strPath.empty() && strPath.at(0) == '$') + strPath = CUtil::TranslateSpecialSource(strPath); + + // need to check path validity again as CUtil::TranslateSpecialSource() may have failed + if (!strPath.empty()) + { + URIUtils::AddSlashAtEnd(strPath); + vecPaths.push_back(strPath); + } + } + else + CLog::Log(LOGERROR, "CMediaSourceSettings: invalid path type ({}) in source", strPath); + } + + pPathName = pPathName->NextSiblingElement("path"); + } + + const TiXmlNode *pLockMode = source->FirstChild("lockmode"); + const TiXmlNode *pLockCode = source->FirstChild("lockcode"); + const TiXmlNode *pBadPwdCount = source->FirstChild("badpwdcount"); + const TiXmlNode *pThumbnailNode = source->FirstChild("thumbnail"); + + if (strName.empty() || vecPaths.empty()) + return false; + + std::vector<std::string> verifiedPaths; + // disallowed for files, or there's only a single path in the vector + if (StringUtils::EqualsNoCase(category, "files") || vecPaths.size() == 1) + verifiedPaths.push_back(vecPaths[0]); + // multiple paths? + else + { + // validate the paths + for (std::vector<std::string>::const_iterator path = vecPaths.begin(); path != vecPaths.end(); ++path) + { + CURL url(*path); + bool bIsInvalid = false; + + // for my programs + if (StringUtils::EqualsNoCase(category, "programs") || StringUtils::EqualsNoCase(category, "myprograms")) + { + // only allow HD and plugins + if (url.IsLocal() || url.IsProtocol("plugin")) + verifiedPaths.push_back(*path); + else + bIsInvalid = true; + } + // for others allow everything (if the user does something silly, we can't stop them) + else + verifiedPaths.push_back(*path); + + // error message + if (bIsInvalid) + CLog::Log(LOGERROR, "CMediaSourceSettings: invalid path type ({}) for multipath source", + path->c_str()); + } + + // no valid paths? skip to next source + if (verifiedPaths.empty()) + { + CLog::Log(LOGERROR,"CMediaSourceSettings: missing or invalid <name> and/or <path> in source"); + return false; + } + } + + share.FromNameAndPaths(category, strName, verifiedPaths); + + share.m_iBadPwdCount = 0; + if (pLockMode) + { + share.m_iLockMode = (LockType)std::strtol(pLockMode->FirstChild()->Value(), NULL, 10); + share.m_iHasLock = LOCK_STATE_LOCKED; + } + + if (pLockCode && pLockCode->FirstChild()) + share.m_strLockCode = pLockCode->FirstChild()->Value(); + + if (pBadPwdCount && pBadPwdCount->FirstChild()) + share.m_iBadPwdCount = (int)std::strtol(pBadPwdCount->FirstChild()->Value(), NULL, 10); + + if (pThumbnailNode && pThumbnailNode->FirstChild()) + share.m_strThumbnailImage = pThumbnailNode->FirstChild()->Value(); + + XMLUtils::GetBoolean(source, "allowsharing", share.m_allowSharing); + + return true; +} + +void CMediaSourceSettings::GetSources(const TiXmlNode* pRootElement, const std::string& strTagName, VECSOURCES& items, std::string& strDefault) +{ + strDefault = ""; + items.clear(); + + const TiXmlNode *pChild = pRootElement->FirstChild(strTagName.c_str()); + if (pChild == NULL) + { + CLog::Log(LOGDEBUG, "CMediaSourceSettings: <{}> tag is missing or sources.xml is malformed", + strTagName); + return; + } + + pChild = pChild->FirstChild(); + while (pChild != NULL) + { + std::string strValue = pChild->ValueStr(); + if (strValue == XML_SOURCE || strValue == "bookmark") // "bookmark" left in for backwards compatibility + { + CMediaSource share; + if (GetSource(strTagName, pChild, share)) + items.push_back(share); + else + CLog::Log(LOGERROR, "CMediaSourceSettings: Missing or invalid <name> and/or <path> in source"); + } + else if (strValue == "default") + { + const TiXmlNode *pValueNode = pChild->FirstChild(); + if (pValueNode) + { + std::string pszText = pChild->FirstChild()->ValueStr(); + if (!pszText.empty()) + strDefault = pszText; + CLog::Log(LOGDEBUG, "CMediaSourceSettings: Setting <default> source to : {}", + strDefault); + } + } + + pChild = pChild->NextSibling(); + } +} + +bool CMediaSourceSettings::SetSources(TiXmlNode *root, const char *section, const VECSOURCES &shares, const std::string &defaultPath) const +{ + TiXmlElement sectionElement(section); + TiXmlNode *sectionNode = root->InsertEndChild(sectionElement); + if (sectionNode == NULL) + return false; + + XMLUtils::SetPath(sectionNode, "default", defaultPath); + for (CIVECSOURCES it = shares.begin(); it != shares.end(); it++) + { + const CMediaSource &share = *it; + if (share.m_ignore) + continue; + + TiXmlElement source(XML_SOURCE); + XMLUtils::SetString(&source, "name", share.strName); + + for (unsigned int i = 0; i < share.vecPaths.size(); i++) + XMLUtils::SetPath(&source, "path", share.vecPaths[i]); + + if (share.m_iHasLock) + { + XMLUtils::SetInt(&source, "lockmode", share.m_iLockMode); + XMLUtils::SetString(&source, "lockcode", share.m_strLockCode); + XMLUtils::SetInt(&source, "badpwdcount", share.m_iBadPwdCount); + } + + if (!share.m_strThumbnailImage.empty()) + XMLUtils::SetPath(&source, "thumbnail", share.m_strThumbnailImage); + + XMLUtils::SetBoolean(&source, "allowsharing", share.m_allowSharing); + + sectionNode->InsertEndChild(source); + } + + return true; +} diff --git a/xbmc/settings/MediaSourceSettings.h b/xbmc/settings/MediaSourceSettings.h new file mode 100644 index 0000000..7fe456e --- /dev/null +++ b/xbmc/settings/MediaSourceSettings.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "MediaSource.h" +#include "settings/lib/ISettingsHandler.h" + +#include <string> + +class CProfileManager; +class TiXmlNode; + +class CMediaSourceSettings : public ISettingsHandler +{ +public: + static CMediaSourceSettings& GetInstance(); + + static std::string GetSourcesFile(); + + void OnSettingsLoaded() override; + void OnSettingsUnloaded() override; + + bool Load(); + bool Load(const std::string &file); + bool Save(); + bool Save(const std::string &file) const; + void Clear(); + + VECSOURCES* GetSources(const std::string &type); + const std::string& GetDefaultSource(const std::string &type) const; + void SetDefaultSource(const std::string &type, const std::string &source); + + bool UpdateSource(const std::string &strType, const std::string &strOldName, const std::string &strUpdateChild, const std::string &strUpdateValue); + bool DeleteSource(const std::string &strType, const std::string &strName, const std::string &strPath, bool virtualSource = false); + bool AddShare(const std::string &type, const CMediaSource &share); + bool UpdateShare(const std::string &type, const std::string &oldName, const CMediaSource &share); + +protected: + CMediaSourceSettings(); + CMediaSourceSettings(const CMediaSourceSettings&) = delete; + CMediaSourceSettings& operator=(CMediaSourceSettings const&) = delete; + ~CMediaSourceSettings() override; + +private: + bool GetSource(const std::string &category, const TiXmlNode *source, CMediaSource &share); + void GetSources(const TiXmlNode* pRootElement, const std::string& strTagName, VECSOURCES& items, std::string& strDefault); + bool SetSources(TiXmlNode *root, const char *section, const VECSOURCES &shares, const std::string &defaultPath) const; + + VECSOURCES m_programSources; + VECSOURCES m_pictureSources; + VECSOURCES m_fileSources; + VECSOURCES m_musicSources; + VECSOURCES m_videoSources; + VECSOURCES m_gameSources; + + std::string m_defaultProgramSource; + std::string m_defaultMusicSource; + std::string m_defaultPictureSource; + std::string m_defaultFileSource; +}; diff --git a/xbmc/settings/SettingAddon.cpp b/xbmc/settings/SettingAddon.cpp new file mode 100644 index 0000000..fd39fe9 --- /dev/null +++ b/xbmc/settings/SettingAddon.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingAddon.h" + +#include "addons/Addon.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "settings/lib/SettingsManager.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <mutex> + +CSettingAddon::CSettingAddon(const std::string &id, CSettingsManager *settingsManager /* = nullptr */) + : CSettingString(id, settingsManager) +{ } + +CSettingAddon::CSettingAddon(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager /* = nullptr */) + : CSettingString(id, label, value, settingsManager) +{ } + +CSettingAddon::CSettingAddon(const std::string &id, const CSettingAddon &setting) + : CSettingString(id, setting) +{ + copyaddontype(setting); +} + +SettingPtr CSettingAddon::Clone(const std::string &id) const +{ + return std::make_shared<CSettingAddon>(id, *this); +} + +bool CSettingAddon::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSettingString::Deserialize(node, update)) + return false; + + if (m_control != nullptr && + (m_control->GetType() != "button" || m_control->GetFormat() != "addon")) + { + CLog::Log(LOGERROR, "CSettingAddon: invalid <control> of \"{}\"", m_id); + return false; + } + + bool ok = false; + std::string strAddonType; + auto constraints = node->FirstChild("constraints"); + if (constraints != nullptr) + { + // get the addon type + if (XMLUtils::GetString(constraints, "addontype", strAddonType) && !strAddonType.empty()) + { + m_addonType = ADDON::CAddonInfo::TranslateType(strAddonType); + if (m_addonType != ADDON::AddonType::UNKNOWN) + ok = true; + } + } + + if (!ok && !update) + { + CLog::Log(LOGERROR, "CSettingAddon: error reading the addontype value \"{}\" of \"{}\"", + strAddonType, m_id); + return false; + } + + return true; +} + +void CSettingAddon::copyaddontype(const CSettingAddon &setting) +{ + CSettingString::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + m_addonType = setting.m_addonType; +} diff --git a/xbmc/settings/SettingAddon.h b/xbmc/settings/SettingAddon.h new file mode 100644 index 0000000..d4aefc8 --- /dev/null +++ b/xbmc/settings/SettingAddon.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "addons/IAddon.h" +#include "settings/lib/Setting.h" + +namespace ADDON +{ +enum class AddonType; +} + +class CSettingAddon : public CSettingString +{ +public: + CSettingAddon(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingAddon(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager = nullptr); + CSettingAddon(const std::string &id, const CSettingAddon &setting); + ~CSettingAddon() override = default; + + SettingPtr Clone(const std::string &id) const override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + ADDON::AddonType GetAddonType() const { return m_addonType; } + void SetAddonType(ADDON::AddonType addonType) { m_addonType = addonType; } + +private: + void copyaddontype(const CSettingAddon &setting); + + ADDON::AddonType m_addonType{}; +}; diff --git a/xbmc/settings/SettingConditions.cpp b/xbmc/settings/SettingConditions.cpp new file mode 100644 index 0000000..bb85e97 --- /dev/null +++ b/xbmc/settings/SettingConditions.cpp @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingConditions.h" + +#include "LockType.h" +#include "addons/AddonManager.h" +#include "addons/Skin.h" +#include "addons/addoninfo/AddonType.h" +#include "application/AppParams.h" +#include "cores/AudioEngine/Engines/ActiveAE/ActiveAESettings.h" +#include "ServiceBroker.h" +#include "GUIPassword.h" +#if defined(HAS_WEB_SERVER) +#include "network/WebServer.h" +#endif +#include "peripherals/Peripherals.h" +#include "profiles/ProfileManager.h" +#include "settings/SettingAddon.h" +#include "settings/SettingsComponent.h" +#include "utils/FontUtils.h" +#include "utils/StringUtils.h" +#include "windowing/WinSystem.h" + +namespace +{ +bool AddonHasSettings(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + if (setting == NULL) + return false; + + std::shared_ptr<const CSettingAddon> settingAddon = std::dynamic_pointer_cast<const CSettingAddon>(setting); + if (settingAddon == NULL) + return false; + + ADDON::AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(settingAddon->GetValue(), addon, + settingAddon->GetAddonType(), + ADDON::OnlyEnabled::CHOICE_YES) || + addon == NULL) + return false; + + if (addon->Type() == ADDON::AddonType::SKIN) + return ((ADDON::CSkinInfo*)addon.get())->HasSkinFile("SkinSettings.xml"); + + return addon->CanHaveAddonOrInstanceSettings(); +} + +bool CheckMasterLock(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return g_passwordManager.IsMasterLockUnlocked(StringUtils::EqualsNoCase(value, "true")); +} + +bool HasPeripherals(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetPeripherals().GetNumberOfPeripherals() > 0; +} + +bool HasPeripheralLibraries(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetAddonMgr().HasInstalledAddons(ADDON::AddonType::PERIPHERALDLL); +} + +bool HasRumbleFeature(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetPeripherals().SupportsFeature(PERIPHERALS::FEATURE_RUMBLE); +} + +bool HasRumbleController(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetPeripherals().HasPeripheralWithFeature(PERIPHERALS::FEATURE_RUMBLE); +} + +bool HasPowerOffFeature(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetPeripherals().SupportsFeature(PERIPHERALS::FEATURE_POWER_OFF); +} + +bool IsHDRDisplay(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetWinSystem()->IsHDRDisplay(); +} + +bool IsMasterUser(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return g_passwordManager.bMasterUser; +} + +bool HasSubtitlesFontExtensions(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + auto settingStr = std::dynamic_pointer_cast<const CSettingString>(setting); + if (!settingStr) + return false; + + return UTILS::FONT::IsSupportedFontExtension(settingStr->GetValue()); +} + +bool ProfileCanWriteDatabase(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().canWriteDatabases(); +} + +bool ProfileCanWriteSources(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().canWriteSources(); +} + +bool ProfileHasAddons(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().hasAddons(); +} + +bool ProfileHasDatabase(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().hasDatabases(); +} + +bool ProfileHasSources(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().hasSources(); +} + +bool ProfileHasAddonManagerLocked(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().addonmanagerLocked(); +} + +bool ProfileHasFilesLocked(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().filesLocked(); +} + +bool ProfileHasMusicLocked(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().musicLocked(); +} + +bool ProfileHasPicturesLocked(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().picturesLocked(); +} + +bool ProfileHasProgramsLocked(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().programsLocked(); +} + +bool ProfileHasSettingsLocked(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + LOCK_LEVEL::SETTINGS_LOCK slValue=LOCK_LEVEL::ALL; + if (StringUtils::EqualsNoCase(value, "none")) + slValue = LOCK_LEVEL::NONE; + else if (StringUtils::EqualsNoCase(value, "standard")) + slValue = LOCK_LEVEL::STANDARD; + else if (StringUtils::EqualsNoCase(value, "advanced")) + slValue = LOCK_LEVEL::ADVANCED; + else if (StringUtils::EqualsNoCase(value, "expert")) + slValue = LOCK_LEVEL::EXPERT; + return slValue <= CSettingConditions::GetCurrentProfile().settingsLockLevel(); +} + +bool ProfileHasVideosLocked(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CSettingConditions::GetCurrentProfile().videoLocked(); +} + +bool ProfileLockMode(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + char* tmp = nullptr; + LockType lock = (LockType)strtol(value.c_str(), &tmp, 0); + if (tmp != NULL && *tmp != '\0') + return false; + + return CSettingConditions::GetCurrentProfile().getLockMode() == lock; +} + +bool GreaterThan(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + if (setting == NULL) + return false; + + std::shared_ptr<const CSettingInt> settingInt = std::dynamic_pointer_cast<const CSettingInt>(setting); + if (settingInt == NULL) + return false; + + char* tmp = nullptr; + + int lhs = settingInt->GetValue(); + int rhs = StringUtils::IsInteger(value) ? (int)strtol(value.c_str(), &tmp, 0) : 0; + + return lhs > rhs; +} + +bool GreaterThanOrEqual(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + if (setting == NULL) + return false; + + std::shared_ptr<const CSettingInt> settingInt = std::dynamic_pointer_cast<const CSettingInt>(setting); + if (settingInt == NULL) + return false; + + char* tmp = nullptr; + + int lhs = settingInt->GetValue(); + int rhs = StringUtils::IsInteger(value) ? (int)strtol(value.c_str(), &tmp, 0) : 0; + + return lhs >= rhs; +} + +bool LessThan(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + if (setting == NULL) + return false; + + std::shared_ptr<const CSettingInt> settingInt = std::dynamic_pointer_cast<const CSettingInt>(setting); + if (settingInt == NULL) + return false; + + char* tmp = nullptr; + + int lhs = settingInt->GetValue(); + int rhs = StringUtils::IsInteger(value) ? (int)strtol(value.c_str(), &tmp, 0) : 0; + + return lhs < rhs; +} + +bool LessThanOrEqual(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + if (setting == NULL) + return false; + + std::shared_ptr<const CSettingInt> settingInt = std::dynamic_pointer_cast<const CSettingInt>(setting); + if (settingInt == NULL) + return false; + + char* tmp = nullptr; + + int lhs = settingInt->GetValue(); + int rhs = StringUtils::IsInteger(value) ? (int)strtol(value.c_str(), &tmp, 0) : 0; + + return lhs <= rhs; +} +}; // anonymous namespace + +const CProfileManager* CSettingConditions::m_profileManager = nullptr; +std::set<std::string> CSettingConditions::m_simpleConditions; +std::map<std::string, SettingConditionCheck> CSettingConditions::m_complexConditions; + +void CSettingConditions::Initialize() +{ + if (!m_simpleConditions.empty()) + return; + + // add simple conditions + m_simpleConditions.emplace("true"); +#ifdef HAS_UPNP + m_simpleConditions.emplace("has_upnp"); +#endif +#ifdef HAS_AIRPLAY + m_simpleConditions.emplace("has_airplay"); +#endif +#ifdef HAVE_X11 + m_simpleConditions.emplace("have_x11"); +#endif +#ifdef HAVE_WAYLAND + m_simpleConditions.emplace("have_wayland"); +#endif +#ifdef HAS_GL + m_simpleConditions.emplace("has_gl"); +#endif +#ifdef HAS_GLES + m_simpleConditions.emplace("has_gles"); +#endif +#if HAS_GLES >= 2 + m_simpleConditions.emplace("has_glesv2"); +#endif +#ifdef HAS_TIME_SERVER + m_simpleConditions.emplace("has_time_server"); +#endif +#ifdef HAS_WEB_SERVER + m_simpleConditions.emplace("has_web_server"); +#endif +#ifdef HAS_FILESYSTEM_SMB + m_simpleConditions.emplace("has_filesystem_smb"); +#endif +#ifdef HAS_FILESYSTEM_NFS + m_simpleConditions.insert("has_filesystem_nfs"); +#endif +#ifdef HAS_ZEROCONF + m_simpleConditions.emplace("has_zeroconf"); +#endif +#ifdef HAVE_LIBVA + m_simpleConditions.emplace("have_libva"); +#endif +#ifdef HAVE_LIBVDPAU + m_simpleConditions.emplace("have_libvdpau"); +#endif +#ifdef TARGET_ANDROID + m_simpleConditions.emplace("has_mediacodec"); +#endif +#ifdef TARGET_DARWIN + m_simpleConditions.emplace("HasVTB"); +#endif +#ifdef TARGET_DARWIN_OSX + m_simpleConditions.emplace("have_osx"); +#endif +#ifdef TARGET_DARWIN_IOS + m_simpleConditions.emplace("have_ios"); +#endif +#ifdef TARGET_DARWIN_TVOS + m_simpleConditions.emplace("have_tvos"); +#endif +#if defined(TARGET_WINDOWS) + m_simpleConditions.emplace("has_dx"); + m_simpleConditions.emplace("hasdxva2"); +#endif +#ifdef HAVE_LCMS2 + m_simpleConditions.emplace("have_lcms2"); +#endif + +#ifdef TARGET_ANDROID + m_simpleConditions.emplace("isstandalone"); +#else + if (CServiceBroker::GetAppParams()->IsStandAlone()) + m_simpleConditions.emplace("isstandalone"); +#endif + + m_simpleConditions.emplace("has_ae_quality_levels"); + +#ifdef HAS_WEB_SERVER + if (CWebServer::WebServerSupportsSSL()) + m_simpleConditions.emplace("webserver_has_ssl"); +#endif + +#ifdef HAVE_LIBBLURAY + m_simpleConditions.emplace("have_libbluray"); +#endif + +#ifdef HAS_CDDA_RIPPER + m_simpleConditions.emplace("has_cdda_ripper"); +#endif + +#ifdef HAS_DVD_DRIVE + m_simpleConditions.emplace("has_dvd_drive"); +#endif + + // add complex conditions + m_complexConditions.emplace("addonhassettings", AddonHasSettings); + m_complexConditions.emplace("checkmasterlock", CheckMasterLock); + m_complexConditions.emplace("hasperipherals", HasPeripherals); + m_complexConditions.emplace("hasperipherallibraries", HasPeripheralLibraries); + m_complexConditions.emplace("hasrumblefeature", HasRumbleFeature); + m_complexConditions.emplace("hasrumblecontroller", HasRumbleController); + m_complexConditions.emplace("haspowerofffeature", HasPowerOffFeature); + m_complexConditions.emplace("ishdrdisplay", IsHDRDisplay); + m_complexConditions.emplace("ismasteruser", IsMasterUser); + m_complexConditions.emplace("hassubtitlesfontextensions", HasSubtitlesFontExtensions); + m_complexConditions.emplace("profilecanwritedatabase", ProfileCanWriteDatabase); + m_complexConditions.emplace("profilecanwritesources", ProfileCanWriteSources); + m_complexConditions.emplace("profilehasaddons", ProfileHasAddons); + m_complexConditions.emplace("profilehasdatabase", ProfileHasDatabase); + m_complexConditions.emplace("profilehassources", ProfileHasSources); + m_complexConditions.emplace("profilehasaddonmanagerlocked", ProfileHasAddonManagerLocked); + m_complexConditions.emplace("profilehasfileslocked", ProfileHasFilesLocked); + m_complexConditions.emplace("profilehasmusiclocked", ProfileHasMusicLocked); + m_complexConditions.emplace("profilehaspictureslocked", ProfileHasPicturesLocked); + m_complexConditions.emplace("profilehasprogramslocked", ProfileHasProgramsLocked); + m_complexConditions.emplace("profilehassettingslocked", ProfileHasSettingsLocked); + m_complexConditions.emplace("profilehasvideoslocked", ProfileHasVideosLocked); + m_complexConditions.emplace("profilelockmode", ProfileLockMode); + m_complexConditions.emplace("aesettingvisible", ActiveAE::CActiveAESettings::IsSettingVisible); + m_complexConditions.emplace("gt", GreaterThan); + m_complexConditions.emplace("gte", GreaterThanOrEqual); + m_complexConditions.emplace("lt", LessThan); + m_complexConditions.emplace("lte", LessThanOrEqual); +} + +void CSettingConditions::Deinitialize() +{ + m_profileManager = nullptr; +} + +const CProfile& CSettingConditions::GetCurrentProfile() +{ + if (!m_profileManager) + m_profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager().get(); + + if (m_profileManager) + return m_profileManager->GetCurrentProfile(); + + static CProfile emptyProfile; + return emptyProfile; +} + +bool CSettingConditions::Check(const std::string& condition, + const std::string& value /* = "" */, + const SettingConstPtr& setting /* = NULL */) +{ + if (m_simpleConditions.find(condition) != m_simpleConditions.end()) + return true; + + std::map<std::string, SettingConditionCheck>::const_iterator itCondition = m_complexConditions.find(condition); + if (itCondition != m_complexConditions.end()) + return itCondition->second(condition, value, setting, NULL); + + return Check(condition); +} diff --git a/xbmc/settings/SettingConditions.h b/xbmc/settings/SettingConditions.h new file mode 100644 index 0000000..f51d9e0 --- /dev/null +++ b/xbmc/settings/SettingConditions.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/SettingConditions.h" + +#include <set> +#include <string> + +class CProfile; +class CProfileManager; + +class CSettingConditions +{ +public: + static void Initialize(); + static void Deinitialize(); + + static const CProfile& GetCurrentProfile(); + + static const std::set<std::string>& GetSimpleConditions() { return m_simpleConditions; } + static const std::map<std::string, SettingConditionCheck>& GetComplexConditions() { return m_complexConditions; } + + static bool Check(const std::string& condition, + const std::string& value = "", + const std::shared_ptr<const CSetting>& setting = NULL); + +private: + // Initialization parameters + static const CProfileManager *m_profileManager; + + static std::set<std::string> m_simpleConditions; + static std::map<std::string, SettingConditionCheck> m_complexConditions; +}; diff --git a/xbmc/settings/SettingControl.cpp b/xbmc/settings/SettingControl.cpp new file mode 100644 index 0000000..3c73b89 --- /dev/null +++ b/xbmc/settings/SettingControl.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingControl.h" + +#include "settings/lib/SettingDefinitions.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <vector> + +const char* SHOW_ADDONS_ALL = "all"; +const char* SHOW_ADDONS_INSTALLED = "installed"; +const char* SHOW_ADDONS_INSTALLABLE = "installable"; + +std::shared_ptr<ISettingControl> CSettingControlCreator::CreateControl(const std::string &controlType) const +{ + if (StringUtils::EqualsNoCase(controlType, "toggle")) + return std::make_shared<CSettingControlCheckmark>(); + else if (StringUtils::EqualsNoCase(controlType, "spinner")) + return std::make_shared<CSettingControlSpinner>(); + else if (StringUtils::EqualsNoCase(controlType, "edit")) + return std::make_shared<CSettingControlEdit>(); + else if (StringUtils::EqualsNoCase(controlType, "button")) + return std::make_shared<CSettingControlButton>(); + else if (StringUtils::EqualsNoCase(controlType, "list")) + return std::make_shared<CSettingControlList>(); + else if (StringUtils::EqualsNoCase(controlType, "slider")) + return std::make_shared<CSettingControlSlider>(); + else if (StringUtils::EqualsNoCase(controlType, "range")) + return std::make_shared<CSettingControlRange>(); + else if (StringUtils::EqualsNoCase(controlType, "title")) + return std::make_shared<CSettingControlTitle>(); + else if (StringUtils::EqualsNoCase(controlType, "label")) + return std::make_shared<CSettingControlLabel>(); + else if (StringUtils::EqualsNoCase(controlType, "colorbutton")) + return std::make_shared<CSettingControlColorButton>(); + + return nullptr; +} + +bool CSettingControlCheckmark::SetFormat(const std::string &format) +{ + return format.empty() || StringUtils::EqualsNoCase(format, "boolean"); +} + +bool CSettingControlFormattedRange::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (!ISettingControl::Deserialize(node, update)) + return false; + + if (m_format == "string") + { + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_FORMATLABEL, m_formatLabel); + + // get the minimum label from <setting><constraints><minimum label="X" /> + auto settingNode = node->Parent(); + if (settingNode != nullptr) + { + auto constraintsNode = settingNode->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraintsNode != nullptr) + { + auto minimumNode = constraintsNode->FirstChild(SETTING_XML_ELM_MINIMUM); + if (minimumNode != nullptr) + { + auto minimumElem = minimumNode->ToElement(); + if (minimumElem != nullptr) + { + if (minimumElem->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &m_minimumLabel) != TIXML_SUCCESS) + m_minimumLabel = -1; + } + } + } + } + + if (m_minimumLabel < 0) + { + std::string strFormat; + if (XMLUtils::GetString(node, SETTING_XML_ATTR_FORMAT, strFormat) && !strFormat.empty()) + m_formatString = strFormat; + } + } + + return true; +} + +bool CSettingControlSpinner::SetFormat(const std::string &format) +{ + if (!StringUtils::EqualsNoCase(format, "string") && + !StringUtils::EqualsNoCase(format, "integer") && + !StringUtils::EqualsNoCase(format, "number")) + return false; + + m_format = format; + StringUtils::ToLower(m_format); + + return true; +} + +bool CSettingControlEdit::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (!ISettingControl::Deserialize(node, update)) + return false; + + XMLUtils::GetBoolean(node, SETTING_XML_ELM_CONTROL_HIDDEN, m_hidden); + XMLUtils::GetBoolean(node, SETTING_XML_ELM_CONTROL_VERIFYNEW, m_verifyNewValue); + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_HEADING, m_heading); + + return true; +} + +bool CSettingControlEdit::SetFormat(const std::string &format) +{ + if (!StringUtils::EqualsNoCase(format, "string") && + !StringUtils::EqualsNoCase(format, "integer") && + !StringUtils::EqualsNoCase(format, "number") && + !StringUtils::EqualsNoCase(format, "ip") && + !StringUtils::EqualsNoCase(format, "md5") && + !StringUtils::EqualsNoCase(format, "urlencoded")) + return false; + + m_format = format; + StringUtils::ToLower(m_format); + + return true; +} + +bool CSettingControlButton::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (!ISettingControl::Deserialize(node, update)) + return false; + + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_HEADING, m_heading); + XMLUtils::GetBoolean(node, SETTING_XML_ELM_CONTROL_HIDEVALUE, m_hideValue); + + if (m_format == "action") + { + bool closeDialog = false; + if (XMLUtils::GetBoolean(node, "close", closeDialog)) + m_closeDialog = closeDialog; + std::string strActionData; + if (XMLUtils::GetString(node, SETTING_XML_ELM_DATA, strActionData)) + m_actionData = strActionData; + } + else if (m_format == "addon") + { + std::string strShowAddons; + if (XMLUtils::GetString(node, "show", strShowAddons) && !strShowAddons.empty()) + { + if (StringUtils::EqualsNoCase(strShowAddons, SHOW_ADDONS_ALL)) + { + m_showInstalledAddons = true; + m_showInstallableAddons = true; + } + else if (StringUtils::EqualsNoCase(strShowAddons, SHOW_ADDONS_INSTALLED)) + { + m_showInstalledAddons = true; + m_showInstallableAddons = false; + } + else if (StringUtils::EqualsNoCase(strShowAddons, SHOW_ADDONS_INSTALLABLE)) + { + m_showInstalledAddons = false; + m_showInstallableAddons = true; + } + else + CLog::Log(LOGWARNING, "CSettingControlButton: invalid <show>"); + + auto show = node->FirstChildElement("show"); + if (show != nullptr) + { + const char *strShowDetails = nullptr; + if ((strShowDetails = show->Attribute(SETTING_XML_ATTR_SHOW_DETAILS)) != nullptr) + { + if (StringUtils::EqualsNoCase(strShowDetails, "false") || StringUtils::EqualsNoCase(strShowDetails, "true")) + m_showAddonDetails = StringUtils::EqualsNoCase(strShowDetails, "true"); + else + CLog::Log(LOGWARNING, "CSettingControlButton: error reading \"details\" attribute of <show>"); + } + + if (!m_showInstallableAddons) + { + const char *strShowMore = nullptr; + if ((strShowMore = show->Attribute(SETTING_XML_ATTR_SHOW_MORE)) != nullptr) + { + if (StringUtils::EqualsNoCase(strShowMore, "false") || StringUtils::EqualsNoCase(strShowMore, "true")) + m_showMoreAddons = StringUtils::EqualsNoCase(strShowMore, "true"); + else + CLog::Log(LOGWARNING, "CSettingControlButton: error reading \"more\" attribute of <show>"); + } + } + } + } + } + else if (m_format == "file") + { + bool useThumbs = false; + if (XMLUtils::GetBoolean(node, "usethumbs", useThumbs)) + m_useImageThumbs = useThumbs; + bool useFileDirectories = false; + if (XMLUtils::GetBoolean(node, "treatasfolder", useFileDirectories)) + m_useFileDirectories = useFileDirectories; + } + + return true; +} + +bool CSettingControlButton::SetFormat(const std::string &format) +{ + if (!StringUtils::EqualsNoCase(format, "path") && + !StringUtils::EqualsNoCase(format, "file") && + !StringUtils::EqualsNoCase(format, "image") && + !StringUtils::EqualsNoCase(format, "addon") && + !StringUtils::EqualsNoCase(format, "action") && + !StringUtils::EqualsNoCase(format, "infolabel") && + !StringUtils::EqualsNoCase(format, "date") && + !StringUtils::EqualsNoCase(format, "time")) + return false; + + m_format = format; + StringUtils::ToLower(m_format); + + return true; +} + +bool CSettingControlList::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (!CSettingControlFormattedRange::Deserialize(node, update)) + return false; + + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_HEADING, m_heading); + XMLUtils::GetBoolean(node, SETTING_XML_ELM_CONTROL_MULTISELECT, m_multiselect); + XMLUtils::GetBoolean(node, SETTING_XML_ELM_CONTROL_HIDEVALUE, m_hideValue); + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_ADDBUTTONLABEL, m_addButtonLabel); + + return true; +} + +bool CSettingControlList::SetFormat(const std::string &format) +{ + if (!StringUtils::EqualsNoCase(format, "string") && + !StringUtils::EqualsNoCase(format, "integer")) + return false; + + m_format = format; + StringUtils::ToLower(m_format); + + return true; +} + +bool CSettingControlSlider::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (!ISettingControl::Deserialize(node, update)) + return false; + + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_HEADING, m_heading); + XMLUtils::GetBoolean(node, SETTING_XML_ELM_CONTROL_POPUP, m_popup); + + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_FORMATLABEL, m_formatLabel); + if (m_formatLabel < 0) + { + std::string strFormat; + if (XMLUtils::GetString(node, SETTING_XML_ATTR_FORMAT, strFormat) && !strFormat.empty()) + m_formatString = strFormat; + } + + return true; +} + +bool CSettingControlSlider::SetFormat(const std::string &format) +{ + if (!StringUtils::EqualsNoCase(format, "percentage") && + !StringUtils::EqualsNoCase(format, "integer") && + !StringUtils::EqualsNoCase(format, "number")) + return false; + + m_format = format; + StringUtils::ToLower(m_format); + m_formatString = GetDefaultFormatString(); + + return true; +} + +std::string CSettingControlSlider::GetDefaultFormatString() const +{ + if (m_format == "percentage") + return "{} %"; + if (m_format == "integer") + return "{:d}"; + if (m_format == "number") + return "{:.1f}"; + + return "{}"; +} + +bool CSettingControlRange::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (!ISettingControl::Deserialize(node, update)) + return false; + + auto formatLabel = node->FirstChildElement(SETTING_XML_ELM_CONTROL_FORMATLABEL); + if (formatLabel != nullptr) + { + XMLUtils::GetInt(node, SETTING_XML_ELM_CONTROL_FORMATLABEL, m_formatLabel); + if (m_formatLabel < 0) + return false; + + auto formatValue = formatLabel->Attribute(SETTING_XML_ELM_CONTROL_FORMATVALUE); + if (formatValue != nullptr) + { + if (StringUtils::IsInteger(formatValue)) + m_valueFormatLabel = (int)strtol(formatValue, nullptr, 0); + else + { + m_valueFormat = formatValue; + if (!m_valueFormat.empty()) + m_valueFormatLabel = -1; + } + } + } + + return true; +} + +bool CSettingControlRange::SetFormat(const std::string &format) +{ + if (StringUtils::EqualsNoCase(format, "percentage")) + m_valueFormat = "{} %"; + else if (StringUtils::EqualsNoCase(format, "integer")) + m_valueFormat = "{:d}"; + else if (StringUtils::EqualsNoCase(format, "number")) + m_valueFormat = "{:.1f}"; + else if (StringUtils::EqualsNoCase(format, "date") || + StringUtils::EqualsNoCase(format, "time")) + m_valueFormat.clear(); + else + return false; + + m_format = format; + StringUtils::ToLower(m_format); + + return true; +} + +bool CSettingControlTitle::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (!ISettingControl::Deserialize(node, update)) + return false; + + std::string strTmp; + if (XMLUtils::GetString(node, SETTING_XML_ATTR_SEPARATOR_POSITION, strTmp)) + { + if (!StringUtils::EqualsNoCase(strTmp, "top") && !StringUtils::EqualsNoCase(strTmp, "bottom")) + CLog::Log(LOGWARNING, "CSettingControlTitle: error reading \"value\" attribute of <{}>", + SETTING_XML_ATTR_SEPARATOR_POSITION); + else + m_separatorBelowLabel = StringUtils::EqualsNoCase(strTmp, "bottom"); + } + XMLUtils::GetBoolean(node, SETTING_XML_ATTR_HIDE_SEPARATOR, m_separatorHidden); + + return true; +} + +CSettingControlLabel::CSettingControlLabel() +{ + m_format = "string"; +} + +bool CSettingControlColorButton::SetFormat(const std::string& format) +{ + return format.empty() || StringUtils::EqualsNoCase(format, "string"); +} diff --git a/xbmc/settings/SettingControl.h b/xbmc/settings/SettingControl.h new file mode 100644 index 0000000..c86f040 --- /dev/null +++ b/xbmc/settings/SettingControl.h @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingControl.h" +#include "settings/lib/ISettingControlCreator.h" + +#define SETTING_XML_ELM_CONTROL_FORMATLABEL "formatlabel" +#define SETTING_XML_ELM_CONTROL_HIDDEN "hidden" +#define SETTING_XML_ELM_CONTROL_VERIFYNEW "verifynew" +#define SETTING_XML_ELM_CONTROL_HEADING "heading" +#define SETTING_XML_ELM_CONTROL_HIDEVALUE "hidevalue" +#define SETTING_XML_ELM_CONTROL_MULTISELECT "multiselect" +#define SETTING_XML_ELM_CONTROL_POPUP "popup" +#define SETTING_XML_ELM_CONTROL_FORMATVALUE "value" +#define SETTING_XML_ELM_CONTROL_ADDBUTTONLABEL "addbuttonlabel" +#define SETTING_XML_ATTR_SHOW_MORE "more" +#define SETTING_XML_ATTR_SHOW_DETAILS "details" +#define SETTING_XML_ATTR_SEPARATOR_POSITION "separatorposition" +#define SETTING_XML_ATTR_HIDE_SEPARATOR "hideseparator" + +class CVariant; + +class CSettingControlCreator : public ISettingControlCreator +{ +public: + // implementation of ISettingControlCreator + std::shared_ptr<ISettingControl> CreateControl(const std::string &controlType) const override; + +protected: + CSettingControlCreator() = default; + ~CSettingControlCreator() override = default; +}; + +class CSettingControlCheckmark : public ISettingControl +{ +public: + CSettingControlCheckmark() + { + m_format = "boolean"; + } + ~CSettingControlCheckmark() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "toggle"; } + bool SetFormat(const std::string &format) override; +}; + +class CSettingControlFormattedRange : public ISettingControl +{ +public: + ~CSettingControlFormattedRange() override = default; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + int GetFormatLabel() const { return m_formatLabel; } + void SetFormatLabel(int formatLabel) { m_formatLabel = formatLabel; } + const std::string& GetFormatString() const { return m_formatString; } + void SetFormatString(const std::string &formatString) { m_formatString = formatString; } + int GetMinimumLabel() const { return m_minimumLabel; } + void SetMinimumLabel(int minimumLabel) { m_minimumLabel = minimumLabel; } + +protected: + CSettingControlFormattedRange() = default; + + int m_formatLabel = -1; + std::string m_formatString = "{}"; + int m_minimumLabel = -1; +}; + +class CSettingControlSpinner : public CSettingControlFormattedRange +{ +public: + CSettingControlSpinner() = default; + ~CSettingControlSpinner() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "spinner"; } + + // specialization of CSettingControlFormattedRange + bool SetFormat(const std::string &format) override; +}; + +class CSettingControlEdit : public ISettingControl +{ +public: + CSettingControlEdit() + { + m_delayed = true; + } + ~CSettingControlEdit() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "edit"; } + bool Deserialize(const TiXmlNode *node, bool update = false) override; + bool SetFormat(const std::string &format) override; + + bool IsHidden() const { return m_hidden; } + void SetHidden(bool hidden) { m_hidden = hidden; } + bool VerifyNewValue() const { return m_verifyNewValue; } + void SetVerifyNewValue(bool verifyNewValue) { m_verifyNewValue = verifyNewValue; } + int GetHeading() const { return m_heading; } + void SetHeading(int heading) { m_heading = heading; } + +protected: + bool m_hidden = false; + bool m_verifyNewValue = false; + int m_heading = -1; +}; + +class CSettingControlButton : public ISettingControl +{ +public: + CSettingControlButton() = default; + ~CSettingControlButton() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "button"; } + bool Deserialize(const TiXmlNode *node, bool update = false) override; + bool SetFormat(const std::string &format) override; + + int GetHeading() const { return m_heading; } + void SetHeading(int heading) { m_heading = heading; } + bool HideValue() const { return m_hideValue; } + void SetHideValue(bool hideValue) { m_hideValue = hideValue; } + + bool ShowAddonDetails() const { return m_showAddonDetails; } + void SetShowAddonDetails(bool showAddonDetails) { m_showAddonDetails = showAddonDetails; } + bool ShowInstalledAddons() const { return m_showInstalledAddons; } + void SetShowInstalledAddons(bool showInstalledAddons) { m_showInstalledAddons = showInstalledAddons; } + bool ShowInstallableAddons() const { return m_showInstallableAddons; } + void SetShowInstallableAddons(bool showInstallableAddons) { m_showInstallableAddons = showInstallableAddons; } + bool ShowMoreAddons() const { return !m_showInstallableAddons && m_showMoreAddons; } + void SetShowMoreAddons(bool showMoreAddons) { m_showMoreAddons = showMoreAddons; } + + bool UseImageThumbs() const { return m_useImageThumbs; } + void SetUseImageThumbs(bool useImageThumbs) { m_useImageThumbs = useImageThumbs; } + bool UseFileDirectories() const { return m_useFileDirectories; } + void SetUseFileDirectories(bool useFileDirectories) { m_useFileDirectories = useFileDirectories; } + + bool HasActionData() const { return !m_actionData.empty(); } + const std::string& GetActionData() const { return m_actionData; } + void SetActionData(const std::string& actionData) { m_actionData = actionData; } + + bool CloseDialog() const { return m_closeDialog; } + void SetCloseDialog(bool closeDialog) { m_closeDialog = closeDialog; } + +protected: + int m_heading = -1; + bool m_hideValue = false; + + bool m_showAddonDetails = true; + bool m_showInstalledAddons = true; + bool m_showInstallableAddons = false; + bool m_showMoreAddons = true; + + bool m_useImageThumbs = false; + bool m_useFileDirectories = false; + + std::string m_actionData; + bool m_closeDialog = false; +}; + +class CSetting; +using SettingControlListValueFormatter = + std::string (*)(const std::shared_ptr<const CSetting>& setting); + +class CSettingControlList : public CSettingControlFormattedRange +{ +public: + CSettingControlList() = default; + ~CSettingControlList() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "list"; } + + // specialization of CSettingControlFormattedRange + bool Deserialize(const TiXmlNode *node, bool update = false) override; + bool SetFormat(const std::string &format) override; + + int GetHeading() const { return m_heading; } + void SetHeading(int heading) { m_heading = heading; } + bool CanMultiSelect() const { return m_multiselect; } + void SetMultiSelect(bool multiselect) { m_multiselect = multiselect; } + bool HideValue() const { return m_hideValue; } + void SetHideValue(bool hideValue) { m_hideValue = hideValue; } + int GetAddButtonLabel() const { return m_addButtonLabel; } + void SetAddButtonLabel(int label) { m_addButtonLabel = label; } + + SettingControlListValueFormatter GetFormatter() const { return m_formatter; } + void SetFormatter(SettingControlListValueFormatter formatter) { m_formatter = formatter; } + + bool UseDetails() const { return m_useDetails; } + void SetUseDetails(bool useDetails) { m_useDetails = useDetails; } + +protected: + int m_heading = -1; + bool m_multiselect = false; + bool m_hideValue = false; + int m_addButtonLabel = -1; + SettingControlListValueFormatter m_formatter = nullptr; + bool m_useDetails{false}; +}; + +class CSettingControlSlider; +using SettingControlSliderFormatter = + std::string (*)(const std::shared_ptr<const CSettingControlSlider>& control, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum); + +class CSettingControlSlider : public ISettingControl +{ +public: + CSettingControlSlider() = default; + ~CSettingControlSlider() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "slider"; } + bool Deserialize(const TiXmlNode *node, bool update = false) override; + bool SetFormat(const std::string &format) override; + + int GetHeading() const { return m_heading; } + void SetHeading(int heading) { m_heading = heading; } + bool UsePopup() const { return m_popup; } + void SetPopup(bool popup) { m_popup = popup; } + int GetFormatLabel() const { return m_formatLabel; } + void SetFormatLabel(int formatLabel) { m_formatLabel = formatLabel; } + const std::string& GetFormatString() const { return m_formatString; } + void SetFormatString(const std::string &formatString) { m_formatString = formatString; } + std::string GetDefaultFormatString() const; + + SettingControlSliderFormatter GetFormatter() const { return m_formatter; } + void SetFormatter(SettingControlSliderFormatter formatter) { m_formatter = formatter; } + +protected: + int m_heading = -1; + bool m_popup = false; + int m_formatLabel = -1; + std::string m_formatString; + SettingControlSliderFormatter m_formatter = nullptr; +}; + +class CSettingControlRange : public ISettingControl +{ +public: + CSettingControlRange() = default; + ~CSettingControlRange() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "range"; } + bool Deserialize(const TiXmlNode *node, bool update = false) override; + bool SetFormat(const std::string &format) override; + + int GetFormatLabel() const { return m_formatLabel; } + void SetFormatLabel(int formatLabel) { m_formatLabel = formatLabel; } + int GetValueFormatLabel() const { return m_valueFormatLabel; } + void SetValueFormatLabel(int valueFormatLabel) { m_valueFormatLabel = valueFormatLabel; } + const std::string& GetValueFormat() const { return m_valueFormat; } + void SetValueFormat(const std::string &valueFormat) { m_valueFormat = valueFormat; } + +protected: + int m_formatLabel = 21469; + int m_valueFormatLabel = -1; + std::string m_valueFormat = "{}"; +}; + +class CSettingControlTitle : public ISettingControl +{ +public: + CSettingControlTitle() = default; + ~CSettingControlTitle() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "title"; } + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool IsSeparatorHidden() const { return m_separatorHidden; } + void SetSeparatorHidden(bool hidden) { m_separatorHidden = hidden; } + bool IsSeparatorBelowLabel() const { return m_separatorBelowLabel; } + void SetSeparatorBelowLabel(bool below) { m_separatorBelowLabel = below; } + +protected: + bool m_separatorHidden = false; + bool m_separatorBelowLabel = true; +}; + +class CSettingControlLabel : public ISettingControl +{ +public: + CSettingControlLabel(); + ~CSettingControlLabel() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "label"; } +}; + +class CSettingControlColorButton : public ISettingControl +{ +public: + CSettingControlColorButton() { m_format = "string"; } + ~CSettingControlColorButton() override = default; + + // implementation of ISettingControl + std::string GetType() const override { return "colorbutton"; } + bool SetFormat(const std::string& format) override; +}; diff --git a/xbmc/settings/SettingCreator.cpp b/xbmc/settings/SettingCreator.cpp new file mode 100644 index 0000000..afe1d89 --- /dev/null +++ b/xbmc/settings/SettingCreator.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingCreator.h" + +#include "settings/SettingAddon.h" +#include "settings/SettingDateTime.h" +#include "settings/SettingPath.h" +#include "utils/StringUtils.h" + +std::shared_ptr<CSetting> CSettingCreator::CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager /* = nullptr */) const +{ + if (StringUtils::EqualsNoCase(settingType, "addon")) + return std::make_shared<CSettingAddon>(settingId, settingsManager); + else if (StringUtils::EqualsNoCase(settingType, "path")) + return std::make_shared<CSettingPath>(settingId, settingsManager); + else if (StringUtils::EqualsNoCase(settingType, "date")) + return std::make_shared<CSettingDate>(settingId, settingsManager); + else if (StringUtils::EqualsNoCase(settingType, "time")) + return std::make_shared<CSettingTime>(settingId, settingsManager); + + return nullptr; +} diff --git a/xbmc/settings/SettingCreator.h b/xbmc/settings/SettingCreator.h new file mode 100644 index 0000000..68d4588 --- /dev/null +++ b/xbmc/settings/SettingCreator.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingCreator.h" + +class CSettingCreator : public ISettingCreator +{ +public: + // implementation of ISettingCreator + std::shared_ptr<CSetting> CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager = nullptr) const override; + +protected: + CSettingCreator() = default; + ~CSettingCreator() override = default; +}; diff --git a/xbmc/settings/SettingDateTime.cpp b/xbmc/settings/SettingDateTime.cpp new file mode 100644 index 0000000..48a736c --- /dev/null +++ b/xbmc/settings/SettingDateTime.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingDateTime.h" + +#include "XBDateTime.h" +#include "utils/TimeUtils.h" + +#include <shared_mutex> + +CSettingDate::CSettingDate(const std::string &id, CSettingsManager *settingsManager /* = NULL */) + : CSettingString(id, settingsManager) +{ } + +CSettingDate::CSettingDate(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager /* = NULL */) + : CSettingString(id, label, value, settingsManager) +{ } + +CSettingDate::CSettingDate(const std::string &id, const CSettingDate &setting) + : CSettingString(id, setting) +{ } + +SettingPtr CSettingDate::Clone(const std::string &id) const +{ + return std::make_shared<CSettingDate>(id, *this); +} + +bool CSettingDate::CheckValidity(const std::string &value) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + + if (!CSettingString::CheckValidity(value)) + return false; + + return CDateTime::FromDBDate(value).IsValid(); +} + +CDateTime CSettingDate::GetDate() const +{ + return CDateTime::FromDBDate(GetValue()); +} + +bool CSettingDate::SetDate(const CDateTime& date) +{ + return SetValue(date.GetAsDBDate()); +} + +CSettingTime::CSettingTime(const std::string &id, CSettingsManager *settingsManager /* = NULL */) + : CSettingString(id, settingsManager) +{ } + +CSettingTime::CSettingTime(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager /* = NULL */) + : CSettingString(id, label, value, settingsManager) +{ } + +CSettingTime::CSettingTime(const std::string &id, const CSettingTime &setting) + : CSettingString(id, setting) +{ } + +SettingPtr CSettingTime::Clone(const std::string &id) const +{ + return std::make_shared<CSettingTime>(id, *this); +} + +bool CSettingTime::CheckValidity(const std::string &value) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + + if (!CSettingString::CheckValidity(value)) + return false; + + return CDateTime::FromDBTime(value).IsValid(); +} + +CDateTime CSettingTime::GetTime() const +{ + return CDateTime::FromDBTime(GetValue()); +} + +bool CSettingTime::SetTime(const CDateTime& time) +{ + return SetValue(CTimeUtils::WithoutSeconds(time.GetAsDBTime())); +} diff --git a/xbmc/settings/SettingDateTime.h b/xbmc/settings/SettingDateTime.h new file mode 100644 index 0000000..5ee481f --- /dev/null +++ b/xbmc/settings/SettingDateTime.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/Setting.h" + +class CDateTime; + +class CSettingDate : public CSettingString +{ +public: + CSettingDate(const std::string &id, CSettingsManager *settingsManager = NULL); + CSettingDate(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager = NULL); + CSettingDate(const std::string &id, const CSettingDate &setting); + ~CSettingDate() override = default; + + SettingPtr Clone(const std::string &id) const override; + + bool CheckValidity(const std::string &value) const override; + + CDateTime GetDate() const; + bool SetDate(const CDateTime& date); +}; + +class CSettingTime : public CSettingString +{ +public: + CSettingTime(const std::string &id, CSettingsManager *settingsManager = NULL); + CSettingTime(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager = NULL); + CSettingTime(const std::string &id, const CSettingTime &setting); + ~CSettingTime() override = default; + + SettingPtr Clone(const std::string &id) const override; + + bool CheckValidity(const std::string &value) const override; + + CDateTime GetTime() const; + bool SetTime(const CDateTime& time); +}; diff --git a/xbmc/settings/SettingPath.cpp b/xbmc/settings/SettingPath.cpp new file mode 100644 index 0000000..fa215ff --- /dev/null +++ b/xbmc/settings/SettingPath.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingPath.h" + +#include "settings/lib/SettingsManager.h" +#include "utils/FileExtensionProvider.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <mutex> + +#define XML_ELM_DEFAULT "default" +#define XML_ELM_CONSTRAINTS "constraints" + +CSettingPath::CSettingPath(const std::string &id, CSettingsManager *settingsManager /* = nullptr */) + : CSettingString(id, settingsManager) +{ } + +CSettingPath::CSettingPath(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager /* = nullptr */) + : CSettingString(id, label, value, settingsManager) +{ } + +CSettingPath::CSettingPath(const std::string &id, const CSettingPath &setting) + : CSettingString(id, setting) +{ + copy(setting); +} + +SettingPtr CSettingPath::Clone(const std::string &id) const +{ + return std::make_shared<CSettingPath>(id, *this); +} + +bool CSettingPath::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSettingString::Deserialize(node, update)) + return false; + + if (m_control != nullptr && + (m_control->GetType() != "button" || (m_control->GetFormat() != "path" && m_control->GetFormat() != "file" && m_control->GetFormat() != "image"))) + { + CLog::Log(LOGERROR, "CSettingPath: invalid <control> of \"{}\"", m_id); + return false; + } + + auto constraints = node->FirstChild(XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // get writable + XMLUtils::GetBoolean(constraints, "writable", m_writable); + // get hide extensions + XMLUtils::GetBoolean(constraints, "hideextensions", m_hideExtension); + + // get sources + auto sources = constraints->FirstChild("sources"); + if (sources != nullptr) + { + m_sources.clear(); + auto source = sources->FirstChild("source"); + while (source != nullptr) + { + auto child = source->FirstChild(); + if (child != nullptr) + { + const std::string& strSource = child->ValueStr(); + if (!strSource.empty()) + m_sources.push_back(strSource); + } + + source = source->NextSibling("source"); + } + } + + // get masking + auto masking = constraints->FirstChild("masking"); + if (masking != nullptr) + m_masking = masking->FirstChild()->ValueStr(); + } + + return true; +} + +bool CSettingPath::SetValue(const std::string &value) +{ + // for backwards compatibility to Frodo + if (StringUtils::EqualsNoCase(value, "select folder") || + StringUtils::EqualsNoCase(value, "select writable folder")) + return CSettingString::SetValue(""); + + return CSettingString::SetValue(value); +} + +std::string CSettingPath::GetMasking(const CFileExtensionProvider& fileExtensionProvider) const +{ + if (m_masking.empty()) + return m_masking; + + // setup masking + auto audioMask = fileExtensionProvider.GetMusicExtensions(); + auto videoMask = fileExtensionProvider.GetVideoExtensions(); + auto imageMask = fileExtensionProvider.GetPictureExtensions(); + auto execMask = ""; +#if defined(TARGET_WINDOWS) + execMask = ".exe|.bat|.cmd|.py"; +#endif // defined(TARGET_WINDOWS) + + std::string masking = m_masking; + if (masking == "video") + return videoMask; + if (masking == "audio") + return audioMask; + if (masking == "image") + return imageMask; + if (masking == "executable") + return execMask; + + // convert mask qualifiers + StringUtils::Replace(masking, "$AUDIO", audioMask); + StringUtils::Replace(masking, "$VIDEO", videoMask); + StringUtils::Replace(masking, "$IMAGE", imageMask); + StringUtils::Replace(masking, "$EXECUTABLE", execMask); + + return masking; +} + +void CSettingPath::copy(const CSettingPath& setting) +{ + CSettingString::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + m_writable = setting.m_writable; + m_sources = setting.m_sources; + m_hideExtension = setting.m_hideExtension; + m_masking = setting.m_masking; +} diff --git a/xbmc/settings/SettingPath.h b/xbmc/settings/SettingPath.h new file mode 100644 index 0000000..423782a --- /dev/null +++ b/xbmc/settings/SettingPath.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/Setting.h" + +#include <vector> + +class CFileExtensionProvider; + +class CSettingPath : public CSettingString +{ +public: + CSettingPath(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingPath(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager = nullptr); + CSettingPath(const std::string &id, const CSettingPath &setting); + ~CSettingPath() override = default; + + SettingPtr Clone(const std::string &id) const override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + bool SetValue(const std::string &value) override; + + bool Writable() const { return m_writable; } + void SetWritable(bool writable) { m_writable = writable; } + const std::vector<std::string>& GetSources() const { return m_sources; } + void SetSources(const std::vector<std::string> &sources) { m_sources = sources; } + bool HideExtension() const { return m_hideExtension; } + void SetHideExtension(bool hideExtension) { m_hideExtension = hideExtension; } + std::string GetMasking(const CFileExtensionProvider& fileExtensionProvider) const; + void SetMasking(const std::string& masking) { m_masking = masking; } + +private: + using CSettingString::copy; + void copy(const CSettingPath &setting); + + bool m_writable = true; + std::vector<std::string> m_sources; + bool m_hideExtension = false; + std::string m_masking; +}; diff --git a/xbmc/settings/SettingUtils.cpp b/xbmc/settings/SettingUtils.cpp new file mode 100644 index 0000000..c690932 --- /dev/null +++ b/xbmc/settings/SettingUtils.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingUtils.h" + +#include "settings/lib/Setting.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <algorithm> + +std::vector<CVariant> CSettingUtils::GetList(const std::shared_ptr<const CSettingList>& settingList) +{ + return ListToValues(settingList, settingList->GetValue()); +} + +bool CSettingUtils::SetList(const std::shared_ptr<CSettingList>& settingList, + const std::vector<CVariant>& value) +{ + SettingList newValues; + if (!ValuesToList(settingList, value, newValues)) + return false; + + return settingList->SetValue(newValues); +} + +std::vector<CVariant> CSettingUtils::ListToValues( + const std::shared_ptr<const CSettingList>& setting, + const std::vector<std::shared_ptr<CSetting>>& values) +{ + std::vector<CVariant> realValues; + + if (setting == NULL) + return realValues; + + for (const auto& value : values) + { + switch (setting->GetElementType()) + { + case SettingType::Boolean: + realValues.emplace_back(std::static_pointer_cast<const CSettingBool>(value)->GetValue()); + break; + + case SettingType::Integer: + realValues.emplace_back(std::static_pointer_cast<const CSettingInt>(value)->GetValue()); + break; + + case SettingType::Number: + realValues.emplace_back(std::static_pointer_cast<const CSettingNumber>(value)->GetValue()); + break; + + case SettingType::String: + realValues.emplace_back(std::static_pointer_cast<const CSettingString>(value)->GetValue()); + break; + + default: + break; + } + } + + return realValues; +} + +bool CSettingUtils::ValuesToList(const std::shared_ptr<const CSettingList>& setting, + const std::vector<CVariant>& values, + std::vector<std::shared_ptr<CSetting>>& newValues) +{ + if (setting == NULL) + return false; + + int index = 0; + bool ret = true; + for (const auto& value : values) + { + SettingPtr settingValue = + setting->GetDefinition()->Clone(StringUtils::Format("{}.{}", setting->GetId(), index++)); + if (settingValue == NULL) + return false; + + switch (setting->GetElementType()) + { + case SettingType::Boolean: + if (!value.isBoolean()) + ret = false; + else + ret = std::static_pointer_cast<CSettingBool>(settingValue)->SetValue(value.asBoolean()); + break; + + case SettingType::Integer: + if (!value.isInteger()) + ret = false; + else + ret = std::static_pointer_cast<CSettingInt>(settingValue)->SetValue(static_cast<int>(value.asInteger())); + break; + + case SettingType::Number: + if (!value.isDouble()) + ret = false; + else + ret = std::static_pointer_cast<CSettingNumber>(settingValue)->SetValue(value.asDouble()); + break; + + case SettingType::String: + if (!value.isString()) + ret = false; + else + ret = std::static_pointer_cast<CSettingString>(settingValue)->SetValue(value.asString()); + break; + + default: + ret = false; + break; + } + + if (!ret) + return false; + + newValues.push_back(std::const_pointer_cast<CSetting>(settingValue)); + } + + return true; +} + +bool CSettingUtils::FindIntInList(const std::shared_ptr<const CSettingList>& settingList, int value) +{ + if (settingList == nullptr || settingList->GetElementType() != SettingType::Integer) + return false; + + const auto values = settingList->GetValue(); + const auto matchingValue = + std::find_if(values.begin(), values.end(), [value](const SettingPtr& setting) { + return std::static_pointer_cast<CSettingInt>(setting)->GetValue() == value; + }); + return matchingValue != values.end(); +} diff --git a/xbmc/settings/SettingUtils.h b/xbmc/settings/SettingUtils.h new file mode 100644 index 0000000..d7e456b --- /dev/null +++ b/xbmc/settings/SettingUtils.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <vector> + +class CVariant; +class CSettingList; +class CSetting; + +class CSettingUtils +{ +public: + /*! + \brief Gets the values of the given list setting. + + \param settingList List setting + \return List of values of the given list setting + */ + static std::vector<CVariant> GetList(const std::shared_ptr<const CSettingList>& settingList); + /*! + \brief Sets the values of the given list setting. + + \param settingList List setting + \param value Values to set + \return True if setting the values was successful, false otherwise + */ + static bool SetList(const std::shared_ptr<CSettingList>& settingList, + const std::vector<CVariant>& value); + + static std::vector<CVariant> ListToValues(const std::shared_ptr<const CSettingList>& setting, + const std::vector<std::shared_ptr<CSetting>>& values); + static bool ValuesToList(const std::shared_ptr<const CSettingList>& setting, + const std::vector<CVariant>& values, + std::vector<std::shared_ptr<CSetting>>& newValues); + + /*! + \brief Search in a list of Ints for a given value. + + \param settingList CSettingList instance + \param value value to search for + \return True if value was found in list, false otherwise + */ + static bool FindIntInList(const std::shared_ptr<const CSettingList>& settingList, int value); +}; diff --git a/xbmc/settings/Settings.cpp b/xbmc/settings/Settings.cpp new file mode 100644 index 0000000..b403b63 --- /dev/null +++ b/xbmc/settings/Settings.cpp @@ -0,0 +1,1088 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Settings.h" + +#include "Autorun.h" +#include "GUIPassword.h" +#include "LangInfo.h" +#include "addons/AddonSystemSettings.h" +#include "addons/Skin.h" +#include "application/AppParams.h" +#include "cores/VideoPlayer/VideoRenderers/BaseRenderer.h" +#include "filesystem/File.h" +#include "guilib/GUIFontManager.h" +#include "guilib/StereoscopicsManager.h" +#include "input/KeyboardLayoutManager.h" + +#include <mutex> +#if defined(TARGET_POSIX) +#include "platform/posix/PosixTimezone.h" +#endif // defined(TARGET_POSIX) +#include "network/upnp/UPnPSettings.h" +#include "network/WakeOnAccess.h" +#if defined(TARGET_DARWIN_OSX) +#include "platform/darwin/osx/XBMCHelper.h" +#endif // defined(TARGET_DARWIN_OSX) +#if defined(TARGET_DARWIN_TVOS) +#include "platform/darwin/tvos/TVOSSettingsHandler.h" +#endif // defined(TARGET_DARWIN_TVOS) +#if defined(TARGET_DARWIN_EMBEDDED) +#include "SettingAddon.h" +#endif +#include "DiscSettings.h" +#include "SeekHandler.h" +#include "ServiceBroker.h" +#include "powermanagement/PowerTypes.h" +#include "profiles/ProfileManager.h" +#include "settings/DisplaySettings.h" +#include "settings/MediaSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/SettingConditions.h" +#include "settings/SettingsComponent.h" +#include "settings/SkinSettings.h" +#include "settings/SubtitlesSettings.h" +#include "settings/lib/SettingsManager.h" +#include "utils/CharsetConverter.h" +#include "utils/RssManager.h" +#include "utils/StringUtils.h" +#include "utils/SystemInfo.h" +#include "utils/Variant.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" +#include "view/ViewStateSettings.h" + +#define SETTINGS_XML_FOLDER "special://xbmc/system/settings/" + +using namespace KODI; +using namespace XFILE; + +//! @todo: remove in c++17 +constexpr const char* CSettings::SETTING_LOOKANDFEEL_SKIN; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_SKINSETTINGS; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_SKINTHEME; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_SKINCOLORS; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_FONT; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_SKINZOOM; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_STARTUPACTION; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_STARTUPWINDOW; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_SOUNDSKIN; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_ENABLERSSFEEDS; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_RSSEDIT; +constexpr const char* CSettings::SETTING_LOOKANDFEEL_STEREOSTRENGTH; +constexpr const char* CSettings::SETTING_LOCALE_LANGUAGE; +constexpr const char* CSettings::SETTING_LOCALE_COUNTRY; +constexpr const char* CSettings::SETTING_LOCALE_CHARSET; +constexpr const char* CSettings::SETTING_LOCALE_KEYBOARDLAYOUTS; +constexpr const char* CSettings::SETTING_LOCALE_ACTIVEKEYBOARDLAYOUT; +constexpr const char* CSettings::SETTING_LOCALE_TIMEZONECOUNTRY; +constexpr const char* CSettings::SETTING_LOCALE_TIMEZONE; +constexpr const char* CSettings::SETTING_LOCALE_SHORTDATEFORMAT; +constexpr const char* CSettings::SETTING_LOCALE_LONGDATEFORMAT; +constexpr const char* CSettings::SETTING_LOCALE_TIMEFORMAT; +constexpr const char* CSettings::SETTING_LOCALE_USE24HOURCLOCK; +constexpr const char* CSettings::SETTING_LOCALE_TEMPERATUREUNIT; +constexpr const char* CSettings::SETTING_LOCALE_SPEEDUNIT; +constexpr const char* CSettings::SETTING_FILELISTS_SHOWPARENTDIRITEMS; +constexpr const char* CSettings::SETTING_FILELISTS_SHOWEXTENSIONS; +constexpr const char* CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING; +constexpr const char* CSettings::SETTING_FILELISTS_ALLOWFILEDELETION; +constexpr const char* CSettings::SETTING_FILELISTS_SHOWADDSOURCEBUTTONS; +constexpr const char* CSettings::SETTING_FILELISTS_SHOWHIDDEN; +constexpr const char* CSettings::SETTING_SCREENSAVER_MODE; +constexpr const char* CSettings::SETTING_SCREENSAVER_SETTINGS; +constexpr const char* CSettings::SETTING_SCREENSAVER_PREVIEW; +constexpr const char* CSettings::SETTING_SCREENSAVER_TIME; +constexpr const char* CSettings::SETTING_SCREENSAVER_USEMUSICVISINSTEAD; +constexpr const char* CSettings::SETTING_SCREENSAVER_USEDIMONPAUSE; +constexpr const char* CSettings::SETTING_WINDOW_WIDTH; +constexpr const char* CSettings::SETTING_WINDOW_HEIGHT; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS; +constexpr const char* CSettings::SETTING_MYVIDEOS_FLATTEN; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_FLATTENTVSHOWS; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_TVSHOWSSELECTFIRSTUNWATCHEDITEM; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_TVSHOWSINCLUDEALLSEASONSANDSPECIALS; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_SHOWALLITEMS; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_UPDATEONSTARTUP; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_BACKGROUNDUPDATE; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_CLEANUP; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_EXPORT; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_IMPORT; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_EPISODEART_WHITELIST; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_MUSICVIDEOART_WHITELIST; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_ARTSETTINGS_UPDATED; +constexpr const char* CSettings::SETTING_VIDEOLIBRARY_SHOWPERFORMERS; +constexpr const char* CSettings::SETTING_LOCALE_AUDIOLANGUAGE; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_PREFERDEFAULTFLAG; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_AUTOPLAYNEXTITEM; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_SEEKSTEPS; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_SEEKDELAY; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_ADJUSTREFRESHRATE; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_ERRORINASPECT; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_STRETCH43; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_TELETEXTENABLED; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_TELETEXTSCALE; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_STEREOSCOPICPLAYBACKMODE; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_QUITSTEREOMODEONSTOP; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_RENDERMETHOD; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_HQSCALERS; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEMEDIACODEC; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEVDPAU; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEVDPAUMIXER; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEVDPAUMPEG2; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEVDPAUMPEG4; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEVDPAUVC1; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEDXVA2; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEVTB; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USEPRIMEDECODER; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_USESTAGEFRIGHT; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_LIMITGUIUPDATE; +constexpr const char* CSettings::SETTING_VIDEOPLAYER_SUPPORTMVC; +constexpr const char* CSettings::SETTING_MYVIDEOS_SELECTACTION; +constexpr const char* CSettings::SETTING_MYVIDEOS_USETAGS; +constexpr const char* CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS; +constexpr const char* CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS; +constexpr const char* CSettings::SETTING_MYVIDEOS_REPLACELABELS; +constexpr const char* CSettings::SETTING_MYVIDEOS_EXTRACTTHUMB; +constexpr const char* CSettings::SETTING_MYVIDEOS_STACKVIDEOS; +constexpr const char* CSettings::SETTING_LOCALE_SUBTITLELANGUAGE; +constexpr const char* CSettings::SETTING_SUBTITLES_PARSECAPTIONS; +constexpr const char* CSettings::SETTING_SUBTITLES_CAPTIONSALIGN; +constexpr const char* CSettings::SETTING_SUBTITLES_ALIGN; +constexpr const char* CSettings::SETTING_SUBTITLES_STEREOSCOPICDEPTH; +constexpr const char* CSettings::SETTING_SUBTITLES_FONTNAME; +constexpr const char* CSettings::SETTING_SUBTITLES_FONTSIZE; +constexpr const char* CSettings::SETTING_SUBTITLES_STYLE; +constexpr const char* CSettings::SETTING_SUBTITLES_COLOR; +constexpr const char* CSettings::SETTING_SUBTITLES_BORDERSIZE; +constexpr const char* CSettings::SETTING_SUBTITLES_BORDERCOLOR; +constexpr const char* CSettings::SETTING_SUBTITLES_BGCOLOR; +constexpr const char* CSettings::SETTING_SUBTITLES_BGOPACITY; +constexpr const char* CSettings::SETTING_SUBTITLES_BLUR; +constexpr const char* CSettings::SETTING_SUBTITLES_BACKGROUNDTYPE; +constexpr const char* CSettings::SETTING_SUBTITLES_SHADOWCOLOR; +constexpr const char* CSettings::SETTING_SUBTITLES_SHADOWOPACITY; +constexpr const char* CSettings::SETTING_SUBTITLES_SHADOWSIZE; +constexpr const char* CSettings::SETTING_SUBTITLES_CHARSET; +constexpr const char* CSettings::SETTING_SUBTITLES_OVERRIDEFONTS; +constexpr const char* CSettings::SETTING_SUBTITLES_OVERRIDESTYLES; +constexpr const char* CSettings::SETTING_SUBTITLES_LANGUAGES; +constexpr const char* CSettings::SETTING_SUBTITLES_STORAGEMODE; +constexpr const char* CSettings::SETTING_SUBTITLES_CUSTOMPATH; +constexpr const char* CSettings::SETTING_SUBTITLES_PAUSEONSEARCH; +constexpr const char* CSettings::SETTING_SUBTITLES_DOWNLOADFIRST; +constexpr const char* CSettings::SETTING_SUBTITLES_TV; +constexpr const char* CSettings::SETTING_SUBTITLES_MOVIE; +constexpr const char* CSettings::SETTING_DVDS_AUTORUN; +constexpr const char* CSettings::SETTING_DVDS_PLAYERREGION; +constexpr const char* CSettings::SETTING_DVDS_AUTOMENU; +constexpr const char* CSettings::SETTING_DISC_PLAYBACK; +constexpr const char* CSettings::SETTING_BLURAY_PLAYERREGION; +constexpr const char* CSettings::SETTING_ACCESSIBILITY_AUDIOVISUAL; +constexpr const char* CSettings::SETTING_ACCESSIBILITY_AUDIOHEARING; +constexpr const char* CSettings::SETTING_ACCESSIBILITY_SUBHEARING; +constexpr const char* CSettings::SETTING_SCRAPERS_MOVIESDEFAULT; +constexpr const char* CSettings::SETTING_SCRAPERS_TVSHOWSDEFAULT; +constexpr const char* CSettings::SETTING_SCRAPERS_MUSICVIDEOSDEFAULT; +constexpr const char* CSettings::SETTING_PVRMANAGER_PRESELECTPLAYINGCHANNEL; +constexpr const char* CSettings::SETTING_PVRMANAGER_SYNCCHANNELGROUPS; +constexpr const char* CSettings::SETTING_PVRMANAGER_BACKENDCHANNELORDER; +constexpr const char* CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERS; +constexpr const char* CSettings::SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERSALWAYS; +constexpr const char* CSettings::SETTING_PVRMANAGER_STARTGROUPCHANNELNUMBERSFROMONE; +constexpr const char* CSettings::SETTING_PVRMANAGER_CLIENTPRIORITIES; +constexpr const char* CSettings::SETTING_PVRMANAGER_CHANNELMANAGER; +constexpr const char* CSettings::SETTING_PVRMANAGER_GROUPMANAGER; +constexpr const char* CSettings::SETTING_PVRMANAGER_CHANNELSCAN; +constexpr const char* CSettings::SETTING_PVRMANAGER_RESETDB; +constexpr const char* CSettings::SETTING_PVRMANAGER_ADDONS; +constexpr const char* CSettings::SETTING_PVRMENU_DISPLAYCHANNELINFO; +constexpr const char* CSettings::SETTING_PVRMENU_CLOSECHANNELOSDONSWITCH; +constexpr const char* CSettings::SETTING_PVRMENU_ICONPATH; +constexpr const char* CSettings::SETTING_PVRMENU_SEARCHICONS; +constexpr const char* CSettings::SETTING_EPG_PAST_DAYSTODISPLAY; +constexpr const char* CSettings::SETTING_EPG_FUTURE_DAYSTODISPLAY; +constexpr const char* CSettings::SETTING_EPG_SELECTACTION; +constexpr const char* CSettings::SETTING_EPG_HIDENOINFOAVAILABLE; +constexpr const char* CSettings::SETTING_EPG_EPGUPDATE; +constexpr const char* CSettings::SETTING_EPG_PREVENTUPDATESWHILEPLAYINGTV; +constexpr const char* CSettings::SETTING_EPG_RESETEPG; +constexpr const char* CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES; +constexpr const char* CSettings::SETTING_PVRPLAYBACK_SIGNALQUALITY; +constexpr const char* CSettings::SETTING_PVRPLAYBACK_CONFIRMCHANNELSWITCH; +constexpr const char* CSettings::SETTING_PVRPLAYBACK_CHANNELENTRYTIMEOUT; +constexpr const char* CSettings::SETTING_PVRPLAYBACK_DELAYMARKLASTWATCHED; +constexpr const char* CSettings::SETTING_PVRPLAYBACK_FPS; +constexpr const char* CSettings::SETTING_PVRRECORD_INSTANTRECORDACTION; +constexpr const char* CSettings::SETTING_PVRRECORD_INSTANTRECORDTIME; +constexpr const char* CSettings::SETTING_PVRRECORD_MARGINSTART; +constexpr const char* CSettings::SETTING_PVRRECORD_MARGINEND; +constexpr const char* CSettings::SETTING_PVRRECORD_TIMERNOTIFICATIONS; +constexpr const char* CSettings::SETTING_PVRRECORD_GROUPRECORDINGS; +constexpr const char* CSettings::SETTING_PVRREMINDERS_AUTOCLOSEDELAY; +constexpr const char* CSettings::SETTING_PVRREMINDERS_AUTORECORD; +constexpr const char* CSettings::SETTING_PVRREMINDERS_AUTOSWITCH; +constexpr const char* CSettings::SETTING_PVRPOWERMANAGEMENT_ENABLED; +constexpr const char* CSettings::SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME; +constexpr const char* CSettings::SETTING_PVRPOWERMANAGEMENT_SETWAKEUPCMD; +constexpr const char* CSettings::SETTING_PVRPOWERMANAGEMENT_PREWAKEUP; +constexpr const char* CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUP; +constexpr const char* CSettings::SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME; +constexpr const char* CSettings::SETTING_PVRPARENTAL_ENABLED; +constexpr const char* CSettings::SETTING_PVRPARENTAL_PIN; +constexpr const char* CSettings::SETTING_PVRPARENTAL_DURATION; +constexpr const char* CSettings::SETTING_PVRCLIENT_MENUHOOK; +constexpr const char* CSettings::SETTING_PVRTIMERS_HIDEDISABLEDTIMERS; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_SHOWDISCS; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_USEORIGINALDATE; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_USEARTISTSORTNAME; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_DOWNLOADINFO; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_PREFERONLINEALBUMART; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_ARTWORKLEVEL; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_USEALLLOCALART; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_USEALLREMOTEART; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_ARTISTART_WHITELIST; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_ALBUMART_WHITELIST; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_MUSICTHUMBS; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_ARTSETTINGS_UPDATED; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_ALBUMSSCRAPER; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_ARTISTSSCRAPER; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_OVERRIDETAGS; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_SHOWALLITEMS; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_UPDATEONSTARTUP; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_BACKGROUNDUPDATE; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_CLEANUP; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO; +constexpr const char* CSettings::SETTING_MUSICLIBRARY_IMPORT; +constexpr const char* CSettings::SETTING_MUSICPLAYER_AUTOPLAYNEXTITEM; +constexpr const char* CSettings::SETTING_MUSICPLAYER_QUEUEBYDEFAULT; +constexpr const char* CSettings::SETTING_MUSICPLAYER_SEEKSTEPS; +constexpr const char* CSettings::SETTING_MUSICPLAYER_SEEKDELAY; +constexpr const char* CSettings::SETTING_MUSICPLAYER_REPLAYGAINTYPE; +constexpr const char* CSettings::SETTING_MUSICPLAYER_REPLAYGAINPREAMP; +constexpr const char* CSettings::SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP; +constexpr const char* CSettings::SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING; +constexpr const char* CSettings::SETTING_MUSICPLAYER_CROSSFADE; +constexpr const char* CSettings::SETTING_MUSICPLAYER_CROSSFADEALBUMTRACKS; +constexpr const char* CSettings::SETTING_MUSICPLAYER_VISUALISATION; +constexpr const char* CSettings::SETTING_MUSICFILES_SELECTACTION; +constexpr const char* CSettings::SETTING_MUSICFILES_USETAGS; +constexpr const char* CSettings::SETTING_MUSICFILES_TRACKFORMAT; +constexpr const char* CSettings::SETTING_MUSICFILES_NOWPLAYINGTRACKFORMAT; +constexpr const char* CSettings::SETTING_MUSICFILES_LIBRARYTRACKFORMAT; +constexpr const char* CSettings::SETTING_MUSICFILES_FINDREMOTETHUMBS; +constexpr const char* CSettings::SETTING_AUDIOCDS_AUTOACTION; +constexpr const char* CSettings::SETTING_AUDIOCDS_USECDDB; +constexpr const char* CSettings::SETTING_AUDIOCDS_RECORDINGPATH; +constexpr const char* CSettings::SETTING_AUDIOCDS_TRACKPATHFORMAT; +constexpr const char* CSettings::SETTING_AUDIOCDS_ENCODER; +constexpr const char* CSettings::SETTING_AUDIOCDS_SETTINGS; +constexpr const char* CSettings::SETTING_AUDIOCDS_EJECTONRIP; +constexpr const char* CSettings::SETTING_MYMUSIC_SONGTHUMBINVIS; +constexpr const char* CSettings::SETTING_MYMUSIC_DEFAULTLIBVIEW; +constexpr const char* CSettings::SETTING_PICTURES_USETAGS; +constexpr const char* CSettings::SETTING_PICTURES_GENERATETHUMBS; +constexpr const char* CSettings::SETTING_PICTURES_SHOWVIDEOS; +constexpr const char* CSettings::SETTING_PICTURES_DISPLAYRESOLUTION; +constexpr const char* CSettings::SETTING_SLIDESHOW_STAYTIME; +constexpr const char* CSettings::SETTING_SLIDESHOW_DISPLAYEFFECTS; +constexpr const char* CSettings::SETTING_SLIDESHOW_SHUFFLE; +constexpr const char* CSettings::SETTING_SLIDESHOW_HIGHQUALITYDOWNSCALING; +constexpr const char* CSettings::SETTING_WEATHER_CURRENTLOCATION; +constexpr const char* CSettings::SETTING_WEATHER_ADDON; +constexpr const char* CSettings::SETTING_WEATHER_ADDONSETTINGS; +constexpr const char* CSettings::SETTING_SERVICES_DEVICENAME; +constexpr const char* CSettings::SETTING_SERVICES_DEVICEUUID; +constexpr const char* CSettings::SETTING_SERVICES_UPNP; +constexpr const char* CSettings::SETTING_SERVICES_UPNPSERVER; +constexpr const char* CSettings::SETTING_SERVICES_UPNPANNOUNCE; +constexpr const char* CSettings::SETTING_SERVICES_UPNPLOOKFOREXTERNALSUBTITLES; +constexpr const char* CSettings::SETTING_SERVICES_UPNPCONTROLLER; +constexpr const char* CSettings::SETTING_SERVICES_UPNPRENDERER; +constexpr const char* CSettings::SETTING_SERVICES_WEBSERVER; +constexpr const char* CSettings::SETTING_SERVICES_WEBSERVERPORT; +constexpr const char* CSettings::SETTING_SERVICES_WEBSERVERAUTHENTICATION; +constexpr const char* CSettings::SETTING_SERVICES_WEBSERVERUSERNAME; +constexpr const char* CSettings::SETTING_SERVICES_WEBSERVERPASSWORD; +constexpr const char* CSettings::SETTING_SERVICES_WEBSERVERSSL; +constexpr const char* CSettings::SETTING_SERVICES_WEBSKIN; +constexpr const char* CSettings::SETTING_SERVICES_ESENABLED; +constexpr const char* CSettings::SETTING_SERVICES_ESPORT; +constexpr const char* CSettings::SETTING_SERVICES_ESPORTRANGE; +constexpr const char* CSettings::SETTING_SERVICES_ESMAXCLIENTS; +constexpr const char* CSettings::SETTING_SERVICES_ESALLINTERFACES; +constexpr const char* CSettings::SETTING_SERVICES_ESINITIALDELAY; +constexpr const char* CSettings::SETTING_SERVICES_ESCONTINUOUSDELAY; +constexpr const char* CSettings::SETTING_SERVICES_ZEROCONF; +constexpr const char* CSettings::SETTING_SERVICES_AIRPLAY; +constexpr const char* CSettings::SETTING_SERVICES_AIRPLAYVOLUMECONTROL; +constexpr const char* CSettings::SETTING_SERVICES_USEAIRPLAYPASSWORD; +constexpr const char* CSettings::SETTING_SERVICES_AIRPLAYPASSWORD; +constexpr const char* CSettings::SETTING_SERVICES_AIRPLAYVIDEOSUPPORT; +constexpr const char* CSettings::SETTING_SMB_WINSSERVER; +constexpr const char* CSettings::SETTING_SMB_WORKGROUP; +constexpr const char* CSettings::SETTING_SMB_MINPROTOCOL; +constexpr const char* CSettings::SETTING_SMB_MAXPROTOCOL; +constexpr const char* CSettings::SETTING_SMB_LEGACYSECURITY; +constexpr const char* CSettings::SETTING_SERVICES_WSDISCOVERY; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_MONITOR; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_SCREEN; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_WHITELIST; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_RESOLUTION; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_SCREENMODE; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_BLANKDISPLAYS; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_STEREOSCOPICMODE; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_PREFEREDSTEREOSCOPICMODE; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_NOOFBUFFERS; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_3DLUT; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_DISPLAYPROFILE; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_TESTPATTERN; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_LIMITEDRANGE; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_FRAMEPACKING; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_10BITSURFACES; +constexpr const char* CSettings::SETTING_VIDEOSCREEN_GUISDRPEAKLUMINANCE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_CHANNELS; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_CONFIG; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_SAMPLERATE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_STEREOUPMIX; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_MAINTAINORIGINALVOLUME; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_PROCESSQUALITY; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_STREAMNOISE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_GUISOUNDMODE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_GUISOUNDVOLUME; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_AC3PASSTHROUGH; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_AC3TRANSCODE; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_EAC3PASSTHROUGH; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_DTSPASSTHROUGH; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_TRUEHDPASSTHROUGH; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_DTSHDPASSTHROUGH; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_DTSHDCOREFALLBACK; +constexpr const char* CSettings::SETTING_AUDIOOUTPUT_VOLUMESTEPS; +constexpr const char* CSettings::SETTING_INPUT_PERIPHERALS; +constexpr const char* CSettings::SETTING_INPUT_PERIPHERALLIBRARIES; +constexpr const char* CSettings::SETTING_INPUT_ENABLEMOUSE; +constexpr const char* CSettings::SETTING_INPUT_ASKNEWCONTROLLERS; +constexpr const char* CSettings::SETTING_INPUT_CONTROLLERCONFIG; +constexpr const char* CSettings::SETTING_INPUT_RUMBLENOTIFY; +constexpr const char* CSettings::SETTING_INPUT_TESTRUMBLE; +constexpr const char* CSettings::SETTING_INPUT_CONTROLLERPOWEROFF; +constexpr const char* CSettings::SETTING_INPUT_APPLEREMOTEMODE; +constexpr const char* CSettings::SETTING_INPUT_APPLEREMOTEALWAYSON; +constexpr const char* CSettings::SETTING_INPUT_APPLEREMOTESEQUENCETIME; +constexpr const char* CSettings::SETTING_INPUT_SIRIREMOTEIDLETIMERENABLED; +constexpr const char* CSettings::SETTING_INPUT_SIRIREMOTEIDLETIME; +constexpr const char* CSettings::SETTING_INPUT_SIRIREMOTEHORIZONTALSENSITIVITY; +constexpr const char* CSettings::SETTING_INPUT_SIRIREMOTEVERTICALSENSITIVITY; +constexpr const char* CSettings::SETTING_INPUT_TVOSUSEKODIKEYBOARD; +constexpr const char* CSettings::SETTING_NETWORK_USEHTTPPROXY; +constexpr const char* CSettings::SETTING_NETWORK_HTTPPROXYTYPE; +constexpr const char* CSettings::SETTING_NETWORK_HTTPPROXYSERVER; +constexpr const char* CSettings::SETTING_NETWORK_HTTPPROXYPORT; +constexpr const char* CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME; +constexpr const char* CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD; +constexpr const char* CSettings::SETTING_NETWORK_BANDWIDTH; +constexpr const char* CSettings::SETTING_POWERMANAGEMENT_DISPLAYSOFF; +constexpr const char* CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME; +constexpr const char* CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE; +constexpr const char* CSettings::SETTING_POWERMANAGEMENT_WAKEONACCESS; +constexpr const char* CSettings::SETTING_POWERMANAGEMENT_WAITFORNETWORK; +constexpr const char* CSettings::SETTING_DEBUG_SHOWLOGINFO; +constexpr const char* CSettings::SETTING_DEBUG_EXTRALOGGING; +constexpr const char* CSettings::SETTING_DEBUG_SETEXTRALOGLEVEL; +constexpr const char* CSettings::SETTING_DEBUG_SCREENSHOTPATH; +constexpr const char* CSettings::SETTING_DEBUG_SHARE_LOG; +constexpr const char* CSettings::SETTING_EVENTLOG_ENABLED; +constexpr const char* CSettings::SETTING_EVENTLOG_ENABLED_NOTIFICATIONS; +constexpr const char* CSettings::SETTING_EVENTLOG_SHOW; +constexpr const char* CSettings::SETTING_MASTERLOCK_LOCKCODE; +constexpr const char* CSettings::SETTING_MASTERLOCK_STARTUPLOCK; +constexpr const char* CSettings::SETTING_MASTERLOCK_MAXRETRIES; +constexpr const char* CSettings::SETTING_CACHE_HARDDISK; +constexpr const char* CSettings::SETTING_CACHEVIDEO_DVDROM; +constexpr const char* CSettings::SETTING_CACHEVIDEO_LAN; +constexpr const char* CSettings::SETTING_CACHEVIDEO_INTERNET; +constexpr const char* CSettings::SETTING_CACHEAUDIO_DVDROM; +constexpr const char* CSettings::SETTING_CACHEAUDIO_LAN; +constexpr const char* CSettings::SETTING_CACHEAUDIO_INTERNET; +constexpr const char* CSettings::SETTING_CACHEDVD_DVDROM; +constexpr const char* CSettings::SETTING_CACHEDVD_LAN; +constexpr const char* CSettings::SETTING_CACHEUNKNOWN_INTERNET; +constexpr const char* CSettings::SETTING_SYSTEM_PLAYLISTSPATH; +constexpr const char* CSettings::SETTING_ADDONS_AUTOUPDATES; +constexpr const char* CSettings::SETTING_ADDONS_NOTIFICATIONS; +constexpr const char* CSettings::SETTING_ADDONS_SHOW_RUNNING; +constexpr const char* CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES; +constexpr const char* CSettings::SETTING_ADDONS_UPDATEMODE; +constexpr const char* CSettings::SETTING_ADDONS_MANAGE_DEPENDENCIES; +constexpr const char* CSettings::SETTING_ADDONS_REMOVE_ORPHANED_DEPENDENCIES; +constexpr const char* CSettings::SETTING_GENERAL_ADDONFOREIGNFILTER; +constexpr const char* CSettings::SETTING_GENERAL_ADDONBROKENFILTER; +constexpr const char* CSettings::SETTING_SOURCE_VIDEOS; +constexpr const char* CSettings::SETTING_SOURCE_MUSIC; +constexpr const char* CSettings::SETTING_SOURCE_PICTURES; +//! @todo: remove in c++17 + +bool CSettings::Initialize() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (m_initialized) + return false; + + // register custom setting types + InitializeSettingTypes(); + // register custom setting controls + InitializeControls(); + + // option fillers and conditions need to be + // initialized before the setting definitions + InitializeOptionFillers(); + InitializeConditions(); + + // load the settings definitions + if (!InitializeDefinitions()) + return false; + + GetSettingsManager()->SetInitialized(); + + InitializeISettingsHandlers(); + InitializeISubSettings(); + InitializeISettingCallbacks(); + + m_initialized = true; + + return true; +} + +void CSettings::RegisterSubSettings(ISubSettings* subSettings) +{ + if (subSettings == nullptr) + return; + + std::unique_lock<CCriticalSection> lock(m_critical); + m_subSettings.insert(subSettings); +} + +void CSettings::UnregisterSubSettings(ISubSettings* subSettings) +{ + if (subSettings == nullptr) + return; + + std::unique_lock<CCriticalSection> lock(m_critical); + m_subSettings.erase(subSettings); +} + +bool CSettings::Load() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + return Load(profileManager->GetSettingsFile()); +} + +bool CSettings::Load(const std::string &file) +{ + CXBMCTinyXML xmlDoc; + bool updated = false; + if (!XFILE::CFile::Exists(file) || !xmlDoc.LoadFile(file) || + !Load(xmlDoc.RootElement(), updated)) + { + CLog::Log(LOGERROR, "CSettings: unable to load settings from {}, creating new default settings", + file); + if (!Reset()) + return false; + + if (!Load(file)) + return false; + } + // if the settings had to be updated, we need to save the changes + else if (updated) + return Save(file); + + return true; +} + +bool CSettings::Load(const TiXmlElement* root) +{ + bool updated = false; + return Load(root, updated); +} + +bool CSettings::Save() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + return Save(profileManager->GetSettingsFile()); +} + +bool CSettings::Save(const std::string &file) +{ + CXBMCTinyXML xmlDoc; + if (!SaveValuesToXml(xmlDoc)) + return false; + + TiXmlElement* root = xmlDoc.RootElement(); + if (root == nullptr) + return false; + + if (!Save(root)) + return false; + + return xmlDoc.SaveFile(file); +} + +bool CSettings::Save(TiXmlNode* root) const +{ + std::unique_lock<CCriticalSection> lock(m_critical); + // save any ISubSettings implementations + for (const auto& subSetting : m_subSettings) + { + if (!subSetting->Save(root)) + return false; + } + + return true; +} + +bool CSettings::LoadSetting(const TiXmlNode *node, const std::string &settingId) +{ + return GetSettingsManager()->LoadSetting(node, settingId); +} + +bool CSettings::GetBool(const std::string& id) const +{ + // Backward compatibility (skins use this setting) + if (StringUtils::EqualsNoCase(id, "lookandfeel.enablemouse")) + return CSettingsBase::GetBool(CSettings::SETTING_INPUT_ENABLEMOUSE); + + return CSettingsBase::GetBool(id); +} + +void CSettings::Clear() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (!m_initialized) + return; + + GetSettingsManager()->Clear(); + + for (auto& subSetting : m_subSettings) + subSetting->Clear(); + + m_initialized = false; +} + +bool CSettings::Load(const TiXmlElement* root, bool& updated) +{ + if (root == nullptr) + return false; + + if (!CSettingsBase::LoadValuesFromXml(root, updated)) + return false; + + return Load(static_cast<const TiXmlNode*>(root)); +} + +bool CSettings::Load(const TiXmlNode* settings) +{ + bool ok = true; + std::unique_lock<CCriticalSection> lock(m_critical); + for (const auto& subSetting : m_subSettings) + ok &= subSetting->Load(settings); + + return ok; +} + +bool CSettings::Initialize(const std::string &file) +{ + CXBMCTinyXML xmlDoc; + if (!xmlDoc.LoadFile(file.c_str())) + { + CLog::Log(LOGERROR, "CSettings: error loading settings definition from {}, Line {}\n{}", file, + xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); + return false; + } + + CLog::Log(LOGDEBUG, "CSettings: loaded settings definition from {}", file); + + return InitializeDefinitionsFromXml(xmlDoc); +} + +bool CSettings::InitializeDefinitions() +{ + if (!Initialize(SETTINGS_XML_FOLDER "settings.xml")) + { + CLog::Log(LOGFATAL, "Unable to load settings definitions"); + return false; + } +#if defined(TARGET_WINDOWS) + if (CFile::Exists(SETTINGS_XML_FOLDER "windows.xml") && !Initialize(SETTINGS_XML_FOLDER "windows.xml")) + CLog::Log(LOGFATAL, "Unable to load windows-specific settings definitions"); +#if defined(TARGET_WINDOWS_DESKTOP) + if (CFile::Exists(SETTINGS_XML_FOLDER "win32.xml") && !Initialize(SETTINGS_XML_FOLDER "win32.xml")) + CLog::Log(LOGFATAL, "Unable to load win32-specific settings definitions"); +#elif defined(TARGET_WINDOWS_STORE) + if (CFile::Exists(SETTINGS_XML_FOLDER "win10.xml") && !Initialize(SETTINGS_XML_FOLDER "win10.xml")) + CLog::Log(LOGFATAL, "Unable to load win10-specific settings definitions"); +#endif +#elif defined(TARGET_ANDROID) + if (CFile::Exists(SETTINGS_XML_FOLDER "android.xml") && !Initialize(SETTINGS_XML_FOLDER "android.xml")) + CLog::Log(LOGFATAL, "Unable to load android-specific settings definitions"); +#elif defined(TARGET_FREEBSD) + if (CFile::Exists(SETTINGS_XML_FOLDER "freebsd.xml") && !Initialize(SETTINGS_XML_FOLDER "freebsd.xml")) + CLog::Log(LOGFATAL, "Unable to load freebsd-specific settings definitions"); +#elif defined(TARGET_LINUX) + if (CFile::Exists(SETTINGS_XML_FOLDER "linux.xml") && !Initialize(SETTINGS_XML_FOLDER "linux.xml")) + CLog::Log(LOGFATAL, "Unable to load linux-specific settings definitions"); +#elif defined(TARGET_DARWIN) + if (CFile::Exists(SETTINGS_XML_FOLDER "darwin.xml") && !Initialize(SETTINGS_XML_FOLDER "darwin.xml")) + CLog::Log(LOGFATAL, "Unable to load darwin-specific settings definitions"); +#if defined(TARGET_DARWIN_OSX) + if (CFile::Exists(SETTINGS_XML_FOLDER "darwin_osx.xml") && !Initialize(SETTINGS_XML_FOLDER "darwin_osx.xml")) + CLog::Log(LOGFATAL, "Unable to load osx-specific settings definitions"); +#elif defined(TARGET_DARWIN_IOS) + if (CFile::Exists(SETTINGS_XML_FOLDER "darwin_ios.xml") && !Initialize(SETTINGS_XML_FOLDER "darwin_ios.xml")) + CLog::Log(LOGFATAL, "Unable to load ios-specific settings definitions"); +#elif defined(TARGET_DARWIN_TVOS) + if (CFile::Exists(SETTINGS_XML_FOLDER "darwin_tvos.xml") && + !Initialize(SETTINGS_XML_FOLDER "darwin_tvos.xml")) + CLog::Log(LOGFATAL, "Unable to load tvos-specific settings definitions"); +#endif +#endif + +#if defined(PLATFORM_SETTINGS_FILE) + if (CFile::Exists(SETTINGS_XML_FOLDER DEF_TO_STR_VALUE(PLATFORM_SETTINGS_FILE)) && !Initialize(SETTINGS_XML_FOLDER DEF_TO_STR_VALUE(PLATFORM_SETTINGS_FILE))) + CLog::Log(LOGFATAL, "Unable to load platform-specific settings definitions ({})", + DEF_TO_STR_VALUE(PLATFORM_SETTINGS_FILE)); +#endif + + // load any custom visibility and default values before loading the special + // appliance.xml so that appliances are able to overwrite even those values + InitializeVisibility(); + InitializeDefaults(); + + if (CFile::Exists(SETTINGS_XML_FOLDER "appliance.xml") && !Initialize(SETTINGS_XML_FOLDER "appliance.xml")) + CLog::Log(LOGFATAL, "Unable to load appliance-specific settings definitions"); + + return true; +} + +void CSettings::InitializeSettingTypes() +{ + GetSettingsManager()->RegisterSettingType("addon", this); + GetSettingsManager()->RegisterSettingType("date", this); + GetSettingsManager()->RegisterSettingType("path", this); + GetSettingsManager()->RegisterSettingType("time", this); +} + +void CSettings::InitializeControls() +{ + GetSettingsManager()->RegisterSettingControl("toggle", this); + GetSettingsManager()->RegisterSettingControl("spinner", this); + GetSettingsManager()->RegisterSettingControl("edit", this); + GetSettingsManager()->RegisterSettingControl("button", this); + GetSettingsManager()->RegisterSettingControl("list", this); + GetSettingsManager()->RegisterSettingControl("slider", this); + GetSettingsManager()->RegisterSettingControl("range", this); + GetSettingsManager()->RegisterSettingControl("title", this); + GetSettingsManager()->RegisterSettingControl("colorbutton", this); +} + +void CSettings::InitializeVisibility() +{ + // hide some settings if necessary +#if defined(TARGET_DARWIN_EMBEDDED) + std::shared_ptr<CSettingString> timezonecountry = std::static_pointer_cast<CSettingString>(GetSettingsManager()->GetSetting(CSettings::SETTING_LOCALE_TIMEZONECOUNTRY)); + std::shared_ptr<CSettingString> timezone = std::static_pointer_cast<CSettingString>(GetSettingsManager()->GetSetting(CSettings::SETTING_LOCALE_TIMEZONE)); + + timezonecountry->SetRequirementsMet(false); + timezone->SetRequirementsMet(false); +#endif +} + +void CSettings::InitializeDefaults() +{ + // set some default values if necessary +#if defined(TARGET_POSIX) + std::shared_ptr<CSettingString> timezonecountry = std::static_pointer_cast<CSettingString>(GetSettingsManager()->GetSetting(CSettings::SETTING_LOCALE_TIMEZONECOUNTRY)); + std::shared_ptr<CSettingString> timezone = std::static_pointer_cast<CSettingString>(GetSettingsManager()->GetSetting(CSettings::SETTING_LOCALE_TIMEZONE)); + + if (timezonecountry->IsVisible()) + timezonecountry->SetDefault(g_timezone.GetCountryByTimezone(g_timezone.GetOSConfiguredTimezone())); + if (timezone->IsVisible()) + timezone->SetDefault(g_timezone.GetOSConfiguredTimezone()); +#endif // defined(TARGET_POSIX) + +#if defined(TARGET_WINDOWS) + // We prefer a fake fullscreen mode (window covering the screen rather than dedicated fullscreen) + // as it works nicer with switching to other applications. However on some systems vsync is broken + // when we do this (eg non-Aero on ATI in particular) and on others (AppleTV) we can't get XBMC to + // the front + if (g_sysinfo.IsAeroDisabled()) + { + auto setting = GetSettingsManager()->GetSetting(CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN); + if (!setting) + CLog::Log(LOGERROR, "Failed to load setting for: {}", + CSettings::SETTING_VIDEOSCREEN_FAKEFULLSCREEN); + else + std::static_pointer_cast<CSettingBool>(setting)->SetDefault(false); + } +#endif + + if (CServiceBroker::GetAppParams()->IsStandAlone()) + { + auto setting = + GetSettingsManager()->GetSetting(CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE); + if (!setting) + CLog::Log(LOGERROR, "Failed to load setting for: {}", + CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNSTATE); + else + std::static_pointer_cast<CSettingInt>(setting)->SetDefault(POWERSTATE_SHUTDOWN); + } + + // Initialize deviceUUID if not already set, used in zeroconf advertisements. + std::shared_ptr<CSettingString> deviceUUID = std::static_pointer_cast<CSettingString>(GetSettingsManager()->GetSetting(CSettings::SETTING_SERVICES_DEVICEUUID)); + if (deviceUUID->GetValue().empty()) + { + const std::string& uuid = StringUtils::CreateUUID(); + auto setting = GetSettingsManager()->GetSetting(CSettings::SETTING_SERVICES_DEVICEUUID); + if (!setting) + CLog::Log(LOGERROR, "Failed to load setting for: {}", CSettings::SETTING_SERVICES_DEVICEUUID); + else + std::static_pointer_cast<CSettingString>(setting)->SetValue(uuid); + } +} + +void CSettings::InitializeOptionFillers() +{ + // register setting option fillers +#ifdef HAS_DVD_DRIVE + GetSettingsManager()->RegisterSettingOptionsFiller("audiocdactions", MEDIA_DETECT::CAutorun::SettingOptionAudioCdActionsFiller); +#endif + GetSettingsManager()->RegisterSettingOptionsFiller("charsets", CCharsetConverter::SettingOptionsCharsetsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("fonts", GUIFontManager::SettingOptionsFontsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller( + "subtitlesfonts", SUBTITLES::CSubtitlesSettings::SettingOptionsSubtitleFontsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("languagenames", CLangInfo::SettingOptionsLanguageNamesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("refreshchangedelays", CDisplaySettings::SettingOptionsRefreshChangeDelaysFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("refreshrates", CDisplaySettings::SettingOptionsRefreshRatesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("regions", CLangInfo::SettingOptionsRegionsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("shortdateformats", CLangInfo::SettingOptionsShortDateFormatsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("longdateformats", CLangInfo::SettingOptionsLongDateFormatsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("timeformats", CLangInfo::SettingOptionsTimeFormatsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("24hourclockformats", CLangInfo::SettingOptions24HourClockFormatsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("speedunits", CLangInfo::SettingOptionsSpeedUnitsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("temperatureunits", CLangInfo::SettingOptionsTemperatureUnitsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("rendermethods", CBaseRenderer::SettingOptionsRenderMethodsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("modes", CDisplaySettings::SettingOptionsModesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("resolutions", CDisplaySettings::SettingOptionsResolutionsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("screens", CDisplaySettings::SettingOptionsDispModeFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("stereoscopicmodes", CDisplaySettings::SettingOptionsStereoscopicModesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("preferedstereoscopicviewmodes", CDisplaySettings::SettingOptionsPreferredStereoscopicViewModesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("monitors", CDisplaySettings::SettingOptionsMonitorsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("cmsmodes", CDisplaySettings::SettingOptionsCmsModesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("cmswhitepoints", CDisplaySettings::SettingOptionsCmsWhitepointsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("cmsprimaries", CDisplaySettings::SettingOptionsCmsPrimariesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("cmsgammamodes", CDisplaySettings::SettingOptionsCmsGammaModesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("videoseeksteps", CSeekHandler::SettingOptionsSeekStepsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("startupwindows", ADDON::CSkinInfo::SettingOptionsStartupWindowsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("audiostreamlanguages", CLangInfo::SettingOptionsAudioStreamLanguagesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("subtitlestreamlanguages", CLangInfo::SettingOptionsSubtitleStreamLanguagesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("subtitledownloadlanguages", CLangInfo::SettingOptionsSubtitleDownloadlanguagesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("iso6391languages", CLangInfo::SettingOptionsISO6391LanguagesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("skincolors", ADDON::CSkinInfo::SettingOptionsSkinColorsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("skinfonts", ADDON::CSkinInfo::SettingOptionsSkinFontsFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("skinthemes", ADDON::CSkinInfo::SettingOptionsSkinThemesFiller); +#ifdef TARGET_LINUX + GetSettingsManager()->RegisterSettingOptionsFiller("timezonecountries", CPosixTimezone::SettingOptionsTimezoneCountriesFiller); + GetSettingsManager()->RegisterSettingOptionsFiller("timezones", CPosixTimezone::SettingOptionsTimezonesFiller); +#endif + GetSettingsManager()->RegisterSettingOptionsFiller("keyboardlayouts", CKeyboardLayoutManager::SettingOptionsKeyboardLayoutsFiller); +} + +void CSettings::UninitializeOptionFillers() +{ + GetSettingsManager()->UnregisterSettingOptionsFiller("audiocdactions"); + GetSettingsManager()->UnregisterSettingOptionsFiller("audiocdencoders"); + GetSettingsManager()->UnregisterSettingOptionsFiller("charsets"); + GetSettingsManager()->UnregisterSettingOptionsFiller("fontheights"); + GetSettingsManager()->UnregisterSettingOptionsFiller("fonts"); + GetSettingsManager()->UnregisterSettingOptionsFiller("subtitlesfonts"); + GetSettingsManager()->UnregisterSettingOptionsFiller("languagenames"); + GetSettingsManager()->UnregisterSettingOptionsFiller("refreshchangedelays"); + GetSettingsManager()->UnregisterSettingOptionsFiller("refreshrates"); + GetSettingsManager()->UnregisterSettingOptionsFiller("regions"); + GetSettingsManager()->UnregisterSettingOptionsFiller("shortdateformats"); + GetSettingsManager()->UnregisterSettingOptionsFiller("longdateformats"); + GetSettingsManager()->UnregisterSettingOptionsFiller("timeformats"); + GetSettingsManager()->UnregisterSettingOptionsFiller("24hourclockformats"); + GetSettingsManager()->UnregisterSettingOptionsFiller("speedunits"); + GetSettingsManager()->UnregisterSettingOptionsFiller("temperatureunits"); + GetSettingsManager()->UnregisterSettingOptionsFiller("rendermethods"); + GetSettingsManager()->UnregisterSettingOptionsFiller("resolutions"); + GetSettingsManager()->UnregisterSettingOptionsFiller("screens"); + GetSettingsManager()->UnregisterSettingOptionsFiller("stereoscopicmodes"); + GetSettingsManager()->UnregisterSettingOptionsFiller("preferedstereoscopicviewmodes"); + GetSettingsManager()->UnregisterSettingOptionsFiller("monitors"); + GetSettingsManager()->UnregisterSettingOptionsFiller("cmsmodes"); + GetSettingsManager()->UnregisterSettingOptionsFiller("cmswhitepoints"); + GetSettingsManager()->UnregisterSettingOptionsFiller("cmsprimaries"); + GetSettingsManager()->UnregisterSettingOptionsFiller("cmsgammamodes"); + GetSettingsManager()->UnregisterSettingOptionsFiller("videoseeksteps"); + GetSettingsManager()->UnregisterSettingOptionsFiller("shutdownstates"); + GetSettingsManager()->UnregisterSettingOptionsFiller("startupwindows"); + GetSettingsManager()->UnregisterSettingOptionsFiller("audiostreamlanguages"); + GetSettingsManager()->UnregisterSettingOptionsFiller("subtitlestreamlanguages"); + GetSettingsManager()->UnregisterSettingOptionsFiller("subtitledownloadlanguages"); + GetSettingsManager()->UnregisterSettingOptionsFiller("iso6391languages"); + GetSettingsManager()->UnregisterSettingOptionsFiller("skincolors"); + GetSettingsManager()->UnregisterSettingOptionsFiller("skinfonts"); + GetSettingsManager()->UnregisterSettingOptionsFiller("skinthemes"); +#if defined(TARGET_LINUX) + GetSettingsManager()->UnregisterSettingOptionsFiller("timezonecountries"); + GetSettingsManager()->UnregisterSettingOptionsFiller("timezones"); +#endif // defined(TARGET_LINUX) + GetSettingsManager()->UnregisterSettingOptionsFiller("verticalsyncs"); + GetSettingsManager()->UnregisterSettingOptionsFiller("keyboardlayouts"); +} + +void CSettings::InitializeConditions() +{ + CSettingConditions::Initialize(); + + // add basic conditions + const std::set<std::string> &simpleConditions = CSettingConditions::GetSimpleConditions(); + for (std::set<std::string>::const_iterator itCondition = simpleConditions.begin(); itCondition != simpleConditions.end(); ++itCondition) + GetSettingsManager()->AddCondition(*itCondition); + + // add more complex conditions + const std::map<std::string, SettingConditionCheck> &complexConditions = CSettingConditions::GetComplexConditions(); + for (std::map<std::string, SettingConditionCheck>::const_iterator itCondition = complexConditions.begin(); itCondition != complexConditions.end(); ++itCondition) + GetSettingsManager()->AddDynamicCondition(itCondition->first, itCondition->second); +} + +void CSettings::UninitializeConditions() +{ + CSettingConditions::Deinitialize(); +} + +void CSettings::InitializeISettingsHandlers() +{ + // register ISettingsHandler implementations + // The order of these matters! Handlers are processed in the order they were registered. + GetSettingsManager()->RegisterSettingsHandler(&CMediaSourceSettings::GetInstance()); +#ifdef HAS_UPNP + GetSettingsManager()->RegisterSettingsHandler(&CUPnPSettings::GetInstance()); +#endif + GetSettingsManager()->RegisterSettingsHandler(&CWakeOnAccess::GetInstance()); + GetSettingsManager()->RegisterSettingsHandler(&CRssManager::GetInstance()); + GetSettingsManager()->RegisterSettingsHandler(&g_langInfo); +#if defined(TARGET_LINUX) && !defined(TARGET_ANDROID) && !defined(__UCLIBC__) + GetSettingsManager()->RegisterSettingsHandler(&g_timezone); +#endif + GetSettingsManager()->RegisterSettingsHandler(&CMediaSettings::GetInstance()); +} + +void CSettings::UninitializeISettingsHandlers() +{ + // unregister ISettingsHandler implementations + GetSettingsManager()->UnregisterSettingsHandler(&CMediaSettings::GetInstance()); +#if defined(TARGET_LINUX) + GetSettingsManager()->UnregisterSettingsHandler(&g_timezone); +#endif // defined(TARGET_LINUX) + GetSettingsManager()->UnregisterSettingsHandler(&g_langInfo); + GetSettingsManager()->UnregisterSettingsHandler(&CRssManager::GetInstance()); + GetSettingsManager()->UnregisterSettingsHandler(&CWakeOnAccess::GetInstance()); +#ifdef HAS_UPNP + GetSettingsManager()->UnregisterSettingsHandler(&CUPnPSettings::GetInstance()); +#endif + GetSettingsManager()->UnregisterSettingsHandler(&CMediaSourceSettings::GetInstance()); +} + +void CSettings::InitializeISubSettings() +{ + // register ISubSettings implementations + RegisterSubSettings(&CDisplaySettings::GetInstance()); + RegisterSubSettings(&CMediaSettings::GetInstance()); + RegisterSubSettings(&CSkinSettings::GetInstance()); + RegisterSubSettings(&g_sysinfo); + RegisterSubSettings(&CViewStateSettings::GetInstance()); +} + +void CSettings::UninitializeISubSettings() +{ + // unregister ISubSettings implementations + UnregisterSubSettings(&CDisplaySettings::GetInstance()); + UnregisterSubSettings(&CMediaSettings::GetInstance()); + UnregisterSubSettings(&CSkinSettings::GetInstance()); + UnregisterSubSettings(&g_sysinfo); + UnregisterSubSettings(&CViewStateSettings::GetInstance()); +} + +void CSettings::InitializeISettingCallbacks() +{ + // register any ISettingCallback implementations + std::set<std::string> settingSet; + settingSet.insert(CSettings::SETTING_MUSICLIBRARY_CLEANUP); + settingSet.insert(CSettings::SETTING_MUSICLIBRARY_EXPORT); + settingSet.insert(CSettings::SETTING_MUSICLIBRARY_IMPORT); + settingSet.insert(CSettings::SETTING_MUSICFILES_TRACKFORMAT); + settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_FLATTENTVSHOWS); + settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_GROUPMOVIESETS); + settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_CLEANUP); + settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_IMPORT); + settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_EXPORT); + settingSet.insert(CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS); + GetSettingsManager()->RegisterCallback(&CMediaSettings::GetInstance(), settingSet); + + settingSet.clear(); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_SCREEN); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_RESOLUTION); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_SCREENMODE); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_MONITOR); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_PREFEREDSTEREOSCOPICMODE); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_3DLUT); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_DISPLAYPROFILE); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_BLANKDISPLAYS); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_WHITELIST); + settingSet.insert(CSettings::SETTING_VIDEOSCREEN_10BITSURFACES); + GetSettingsManager()->RegisterCallback(&CDisplaySettings::GetInstance(), settingSet); + + settingSet.clear(); + settingSet.insert(CSettings::SETTING_SUBTITLES_CHARSET); + settingSet.insert(CSettings::SETTING_LOCALE_CHARSET); + GetSettingsManager()->RegisterCallback(&g_charsetConverter, settingSet); + + settingSet.clear(); + settingSet.insert(CSettings::SETTING_LOCALE_AUDIOLANGUAGE); + settingSet.insert(CSettings::SETTING_LOCALE_SUBTITLELANGUAGE); + settingSet.insert(CSettings::SETTING_LOCALE_LANGUAGE); + settingSet.insert(CSettings::SETTING_LOCALE_COUNTRY); + settingSet.insert(CSettings::SETTING_LOCALE_SHORTDATEFORMAT); + settingSet.insert(CSettings::SETTING_LOCALE_LONGDATEFORMAT); + settingSet.insert(CSettings::SETTING_LOCALE_TIMEFORMAT); + settingSet.insert(CSettings::SETTING_LOCALE_USE24HOURCLOCK); + settingSet.insert(CSettings::SETTING_LOCALE_TEMPERATUREUNIT); + settingSet.insert(CSettings::SETTING_LOCALE_SPEEDUNIT); + GetSettingsManager()->RegisterCallback(&g_langInfo, settingSet); + + settingSet.clear(); + settingSet.insert(CSettings::SETTING_MASTERLOCK_LOCKCODE); + GetSettingsManager()->RegisterCallback(&g_passwordManager, settingSet); + + settingSet.clear(); + settingSet.insert(CSettings::SETTING_LOOKANDFEEL_RSSEDIT); + GetSettingsManager()->RegisterCallback(&CRssManager::GetInstance(), settingSet); + +#if defined(TARGET_LINUX) + settingSet.clear(); + settingSet.insert(CSettings::SETTING_LOCALE_TIMEZONE); + settingSet.insert(CSettings::SETTING_LOCALE_TIMEZONECOUNTRY); + GetSettingsManager()->RegisterCallback(&g_timezone, settingSet); +#endif + +#if defined(TARGET_DARWIN_OSX) + settingSet.clear(); + settingSet.insert(CSettings::SETTING_INPUT_APPLEREMOTEMODE); + settingSet.insert(CSettings::SETTING_INPUT_APPLEREMOTEALWAYSON); + GetSettingsManager()->RegisterCallback(&XBMCHelper::GetInstance(), settingSet); +#endif + +#if defined(TARGET_DARWIN_TVOS) + settingSet.clear(); + settingSet.insert(CSettings::SETTING_INPUT_SIRIREMOTEIDLETIMERENABLED); + settingSet.insert(CSettings::SETTING_INPUT_SIRIREMOTEIDLETIME); + settingSet.insert(CSettings::SETTING_INPUT_SIRIREMOTEHORIZONTALSENSITIVITY); + settingSet.insert(CSettings::SETTING_INPUT_SIRIREMOTEVERTICALSENSITIVITY); + GetSettingsManager()->RegisterCallback(&CTVOSInputSettings::GetInstance(), settingSet); +#endif + + settingSet.clear(); + settingSet.insert(CSettings::SETTING_ADDONS_SHOW_RUNNING); + settingSet.insert(CSettings::SETTING_ADDONS_MANAGE_DEPENDENCIES); + settingSet.insert(CSettings::SETTING_ADDONS_REMOVE_ORPHANED_DEPENDENCIES); + settingSet.insert(CSettings::SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES); + GetSettingsManager()->RegisterCallback(&ADDON::CAddonSystemSettings::GetInstance(), settingSet); + + settingSet.clear(); + settingSet.insert(CSettings::SETTING_POWERMANAGEMENT_WAKEONACCESS); + GetSettingsManager()->RegisterCallback(&CWakeOnAccess::GetInstance(), settingSet); + +#ifdef HAVE_LIBBLURAY + settingSet.clear(); + settingSet.insert(CSettings::SETTING_DISC_PLAYBACK); + GetSettingsManager()->RegisterCallback(&CDiscSettings::GetInstance(), settingSet); +#endif +} + +void CSettings::UninitializeISettingCallbacks() +{ + GetSettingsManager()->UnregisterCallback(&CMediaSettings::GetInstance()); + GetSettingsManager()->UnregisterCallback(&CDisplaySettings::GetInstance()); + GetSettingsManager()->UnregisterCallback(&g_charsetConverter); + GetSettingsManager()->UnregisterCallback(&g_langInfo); + GetSettingsManager()->UnregisterCallback(&g_passwordManager); + GetSettingsManager()->UnregisterCallback(&CRssManager::GetInstance()); +#if defined(TARGET_LINUX) + GetSettingsManager()->UnregisterCallback(&g_timezone); +#endif // defined(TARGET_LINUX) +#if defined(TARGET_DARWIN_OSX) + GetSettingsManager()->UnregisterCallback(&XBMCHelper::GetInstance()); +#endif + GetSettingsManager()->UnregisterCallback(&CWakeOnAccess::GetInstance()); +#ifdef HAVE_LIBBLURAY + GetSettingsManager()->UnregisterCallback(&CDiscSettings::GetInstance()); +#endif +} + +bool CSettings::Reset() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + const std::string settingsFile = profileManager->GetSettingsFile(); + + // try to delete the settings file + if (XFILE::CFile::Exists(settingsFile, false) && !XFILE::CFile::Delete(settingsFile)) + CLog::Log(LOGWARNING, "Unable to delete old settings file at {}", settingsFile); + + // unload any loaded settings + Unload(); + + // try to save the default settings + if (!Save()) + { + CLog::Log(LOGWARNING, "Failed to save the default settings to {}", settingsFile); + return false; + } + + return true; +} diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h new file mode 100644 index 0000000..cdd2d31 --- /dev/null +++ b/xbmc/settings/Settings.h @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/ISubSettings.h" +#include "settings/SettingControl.h" +#include "settings/SettingCreator.h" +#include "settings/SettingsBase.h" + +#include <string> + +class CSettingList; +class TiXmlNode; + +/*! + \brief Wrapper around CSettingsManager responsible for properly setting up + the settings manager and registering all the callbacks, handlers and custom + setting types. + \sa CSettingsManager + */ +class CSettings : public CSettingsBase, public CSettingCreator, public CSettingControlCreator + , private ISubSettings +{ +public: + static constexpr auto SETTING_LOOKANDFEEL_SKIN = "lookandfeel.skin"; + static constexpr auto SETTING_LOOKANDFEEL_SKINSETTINGS = "lookandfeel.skinsettings"; + static constexpr auto SETTING_LOOKANDFEEL_SKINTHEME = "lookandfeel.skintheme"; + static constexpr auto SETTING_LOOKANDFEEL_SKINCOLORS = "lookandfeel.skincolors"; + static constexpr auto SETTING_LOOKANDFEEL_FONT = "lookandfeel.font"; + static constexpr auto SETTING_LOOKANDFEEL_SKINZOOM = "lookandfeel.skinzoom"; + static constexpr auto SETTING_LOOKANDFEEL_STARTUPACTION = "lookandfeel.startupaction"; + static constexpr auto SETTING_LOOKANDFEEL_STARTUPWINDOW = "lookandfeel.startupwindow"; + static constexpr auto SETTING_LOOKANDFEEL_SOUNDSKIN = "lookandfeel.soundskin"; + static constexpr auto SETTING_LOOKANDFEEL_ENABLERSSFEEDS = "lookandfeel.enablerssfeeds"; + static constexpr auto SETTING_LOOKANDFEEL_RSSEDIT = "lookandfeel.rssedit"; + static constexpr auto SETTING_LOOKANDFEEL_STEREOSTRENGTH = "lookandfeel.stereostrength"; + static constexpr auto SETTING_LOCALE_LANGUAGE = "locale.language"; + static constexpr auto SETTING_LOCALE_COUNTRY = "locale.country"; + static constexpr auto SETTING_LOCALE_CHARSET = "locale.charset"; + static constexpr auto SETTING_LOCALE_KEYBOARDLAYOUTS = "locale.keyboardlayouts"; + static constexpr auto SETTING_LOCALE_ACTIVEKEYBOARDLAYOUT = "locale.activekeyboardlayout"; + static constexpr auto SETTING_LOCALE_TIMEZONECOUNTRY = "locale.timezonecountry"; + static constexpr auto SETTING_LOCALE_TIMEZONE = "locale.timezone"; + static constexpr auto SETTING_LOCALE_SHORTDATEFORMAT = "locale.shortdateformat"; + static constexpr auto SETTING_LOCALE_LONGDATEFORMAT = "locale.longdateformat"; + static constexpr auto SETTING_LOCALE_TIMEFORMAT = "locale.timeformat"; + static constexpr auto SETTING_LOCALE_USE24HOURCLOCK = "locale.use24hourclock"; + static constexpr auto SETTING_LOCALE_TEMPERATUREUNIT = "locale.temperatureunit"; + static constexpr auto SETTING_LOCALE_SPEEDUNIT = "locale.speedunit"; + static constexpr auto SETTING_FILELISTS_SHOWPARENTDIRITEMS = "filelists.showparentdiritems"; + static constexpr auto SETTING_FILELISTS_SHOWEXTENSIONS = "filelists.showextensions"; + static constexpr auto SETTING_FILELISTS_IGNORETHEWHENSORTING = "filelists.ignorethewhensorting"; + static constexpr auto SETTING_FILELISTS_ALLOWFILEDELETION = "filelists.allowfiledeletion"; + static constexpr auto SETTING_FILELISTS_SHOWADDSOURCEBUTTONS = "filelists.showaddsourcebuttons"; + static constexpr auto SETTING_FILELISTS_SHOWHIDDEN = "filelists.showhidden"; + static constexpr auto SETTING_SCREENSAVER_MODE = "screensaver.mode"; + static constexpr auto SETTING_SCREENSAVER_SETTINGS = "screensaver.settings"; + static constexpr auto SETTING_SCREENSAVER_PREVIEW = "screensaver.preview"; + static constexpr auto SETTING_SCREENSAVER_TIME = "screensaver.time"; + static constexpr auto SETTING_SCREENSAVER_USEMUSICVISINSTEAD = "screensaver.usemusicvisinstead"; + static constexpr auto SETTING_SCREENSAVER_USEDIMONPAUSE = "screensaver.usedimonpause"; + static constexpr auto SETTING_WINDOW_WIDTH = "window.width"; + static constexpr auto SETTING_WINDOW_HEIGHT = "window.height"; + static constexpr auto SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS = "videolibrary.showunwatchedplots"; + static constexpr auto SETTING_VIDEOLIBRARY_ACTORTHUMBS = "videolibrary.actorthumbs"; + static constexpr auto SETTING_MYVIDEOS_FLATTEN = "myvideos.flatten"; + static constexpr auto SETTING_VIDEOLIBRARY_FLATTENTVSHOWS = "videolibrary.flattentvshows"; + static constexpr auto SETTING_VIDEOLIBRARY_TVSHOWSSELECTFIRSTUNWATCHEDITEM = + "videolibrary.tvshowsselectfirstunwatcheditem"; + static constexpr auto SETTING_VIDEOLIBRARY_TVSHOWSINCLUDEALLSEASONSANDSPECIALS = + "videolibrary.tvshowsincludeallseasonsandspecials"; + static constexpr auto SETTING_VIDEOLIBRARY_SHOWALLITEMS = "videolibrary.showallitems"; + static constexpr auto SETTING_VIDEOLIBRARY_GROUPMOVIESETS = "videolibrary.groupmoviesets"; + static constexpr auto SETTING_VIDEOLIBRARY_GROUPSINGLEITEMSETS = + "videolibrary.groupsingleitemsets"; + static constexpr auto SETTING_VIDEOLIBRARY_UPDATEONSTARTUP = "videolibrary.updateonstartup"; + static constexpr auto SETTING_VIDEOLIBRARY_BACKGROUNDUPDATE = "videolibrary.backgroundupdate"; + static constexpr auto SETTING_VIDEOLIBRARY_CLEANUP = "videolibrary.cleanup"; + static constexpr auto SETTING_VIDEOLIBRARY_EXPORT = "videolibrary.export"; + static constexpr auto SETTING_VIDEOLIBRARY_IMPORT = "videolibrary.import"; + static constexpr auto SETTING_VIDEOLIBRARY_SHOWEMPTYTVSHOWS = "videolibrary.showemptytvshows"; + static constexpr auto SETTING_VIDEOLIBRARY_MOVIESETSFOLDER = "videolibrary.moviesetsfolder"; + static constexpr auto SETTING_VIDEOLIBRARY_ARTWORK_LEVEL = "videolibrary.artworklevel"; + static constexpr auto SETTING_VIDEOLIBRARY_MOVIEART_WHITELIST = "videolibrary.movieartwhitelist"; + static constexpr auto SETTING_VIDEOLIBRARY_TVSHOWART_WHITELIST = + "videolibrary.tvshowartwhitelist"; + static constexpr auto SETTING_VIDEOLIBRARY_EPISODEART_WHITELIST = + "videolibrary.episodeartwhitelist"; + static constexpr auto SETTING_VIDEOLIBRARY_MUSICVIDEOART_WHITELIST = + "videolibrary.musicvideoartwhitelist"; + static constexpr auto SETTING_VIDEOLIBRARY_ARTSETTINGS_UPDATED = + "videolibrary.artsettingsupdated"; + static constexpr auto SETTING_VIDEOLIBRARY_SHOWPERFORMERS = + "videolibrary.musicvideosallperformers"; + static constexpr auto SETTING_LOCALE_AUDIOLANGUAGE = "locale.audiolanguage"; + static constexpr auto SETTING_VIDEOPLAYER_PREFERDEFAULTFLAG = "videoplayer.preferdefaultflag"; + static constexpr auto SETTING_VIDEOPLAYER_AUTOPLAYNEXTITEM = "videoplayer.autoplaynextitem"; + static constexpr auto SETTING_VIDEOPLAYER_SEEKSTEPS = "videoplayer.seeksteps"; + static constexpr auto SETTING_VIDEOPLAYER_SEEKDELAY = "videoplayer.seekdelay"; + static constexpr auto SETTING_VIDEOPLAYER_ADJUSTREFRESHRATE = "videoplayer.adjustrefreshrate"; + static constexpr auto SETTING_VIDEOPLAYER_USEDISPLAYASCLOCK = "videoplayer.usedisplayasclock"; + static constexpr auto SETTING_VIDEOPLAYER_ERRORINASPECT = "videoplayer.errorinaspect"; + static constexpr auto SETTING_VIDEOPLAYER_STRETCH43 = "videoplayer.stretch43"; + static constexpr auto SETTING_VIDEOPLAYER_TELETEXTENABLED = "videoplayer.teletextenabled"; + static constexpr auto SETTING_VIDEOPLAYER_TELETEXTSCALE = "videoplayer.teletextscale"; + static constexpr auto SETTING_VIDEOPLAYER_STEREOSCOPICPLAYBACKMODE = + "videoplayer.stereoscopicplaybackmode"; + static constexpr auto SETTING_VIDEOPLAYER_QUITSTEREOMODEONSTOP = + "videoplayer.quitstereomodeonstop"; + static constexpr auto SETTING_VIDEOPLAYER_RENDERMETHOD = "videoplayer.rendermethod"; + static constexpr auto SETTING_VIDEOPLAYER_HQSCALERS = "videoplayer.hqscalers"; + static constexpr auto SETTING_VIDEOPLAYER_USEMEDIACODEC = "videoplayer.usemediacodec"; + static constexpr auto SETTING_VIDEOPLAYER_USEMEDIACODECSURFACE = + "videoplayer.usemediacodecsurface"; + static constexpr auto SETTING_VIDEOPLAYER_USEVDPAU = "videoplayer.usevdpau"; + static constexpr auto SETTING_VIDEOPLAYER_USEVDPAUMIXER = "videoplayer.usevdpaumixer"; + static constexpr auto SETTING_VIDEOPLAYER_USEVDPAUMPEG2 = "videoplayer.usevdpaumpeg2"; + static constexpr auto SETTING_VIDEOPLAYER_USEVDPAUMPEG4 = "videoplayer.usevdpaumpeg4"; + static constexpr auto SETTING_VIDEOPLAYER_USEVDPAUVC1 = "videoplayer.usevdpauvc1"; + static constexpr auto SETTING_VIDEOPLAYER_USEDXVA2 = "videoplayer.usedxva2"; + static constexpr auto SETTING_VIDEOPLAYER_USEVTB = "videoplayer.usevtb"; + static constexpr auto SETTING_VIDEOPLAYER_USEPRIMEDECODER = "videoplayer.useprimedecoder"; + static constexpr auto SETTING_VIDEOPLAYER_USESTAGEFRIGHT = "videoplayer.usestagefright"; + static constexpr auto SETTING_VIDEOPLAYER_LIMITGUIUPDATE = "videoplayer.limitguiupdate"; + static constexpr auto SETTING_VIDEOPLAYER_SUPPORTMVC = "videoplayer.supportmvc"; + static constexpr auto SETTING_MYVIDEOS_SELECTACTION = "myvideos.selectaction"; + static constexpr auto SETTING_MYVIDEOS_USETAGS = "myvideos.usetags"; + static constexpr auto SETTING_MYVIDEOS_EXTRACTFLAGS = "myvideos.extractflags"; + static constexpr auto SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS = "myvideos.extractchapterthumbs"; + static constexpr auto SETTING_MYVIDEOS_REPLACELABELS = "myvideos.replacelabels"; + static constexpr auto SETTING_MYVIDEOS_EXTRACTTHUMB = "myvideos.extractthumb"; + static constexpr auto SETTING_MYVIDEOS_STACKVIDEOS = "myvideos.stackvideos"; + static constexpr auto SETTING_LOCALE_SUBTITLELANGUAGE = "locale.subtitlelanguage"; + static constexpr auto SETTING_SUBTITLES_PARSECAPTIONS = "subtitles.parsecaptions"; + static constexpr auto SETTING_SUBTITLES_CAPTIONSALIGN = "subtitles.captionsalign"; + static constexpr auto SETTING_SUBTITLES_ALIGN = "subtitles.align"; + static constexpr auto SETTING_SUBTITLES_STEREOSCOPICDEPTH = "subtitles.stereoscopicdepth"; + static constexpr auto SETTING_SUBTITLES_FONTNAME = "subtitles.fontname"; + static constexpr auto SETTING_SUBTITLES_FONTSIZE = "subtitles.fontsize"; + static constexpr auto SETTING_SUBTITLES_STYLE = "subtitles.style"; + static constexpr auto SETTING_SUBTITLES_COLOR = "subtitles.colorpick"; + static constexpr auto SETTING_SUBTITLES_BORDERSIZE = "subtitles.bordersize"; + static constexpr auto SETTING_SUBTITLES_BORDERCOLOR = "subtitles.bordercolorpick"; + static constexpr auto SETTING_SUBTITLES_OPACITY = "subtitles.opacity"; + static constexpr auto SETTING_SUBTITLES_BLUR = "subtitles.blur"; + static constexpr auto SETTING_SUBTITLES_BACKGROUNDTYPE = "subtitles.backgroundtype"; + static constexpr auto SETTING_SUBTITLES_SHADOWCOLOR = "subtitles.shadowcolor"; + static constexpr auto SETTING_SUBTITLES_SHADOWOPACITY = "subtitles.shadowopacity"; + static constexpr auto SETTING_SUBTITLES_SHADOWSIZE = "subtitles.shadowsize"; + static constexpr auto SETTING_SUBTITLES_BGCOLOR = "subtitles.bgcolorpick"; + static constexpr auto SETTING_SUBTITLES_BGOPACITY = "subtitles.bgopacity"; + static constexpr auto SETTING_SUBTITLES_MARGINVERTICAL = "subtitles.marginvertical"; + static constexpr auto SETTING_SUBTITLES_CHARSET = "subtitles.charset"; + static constexpr auto SETTING_SUBTITLES_OVERRIDEFONTS = "subtitles.overridefonts"; + static constexpr auto SETTING_SUBTITLES_OVERRIDESTYLES = "subtitles.overridestyles"; + static constexpr auto SETTING_SUBTITLES_LANGUAGES = "subtitles.languages"; + static constexpr auto SETTING_SUBTITLES_STORAGEMODE = "subtitles.storagemode"; + static constexpr auto SETTING_SUBTITLES_CUSTOMPATH = "subtitles.custompath"; + static constexpr auto SETTING_SUBTITLES_PAUSEONSEARCH = "subtitles.pauseonsearch"; + static constexpr auto SETTING_SUBTITLES_DOWNLOADFIRST = "subtitles.downloadfirst"; + static constexpr auto SETTING_SUBTITLES_TV = "subtitles.tv"; + static constexpr auto SETTING_SUBTITLES_MOVIE = "subtitles.movie"; + static constexpr auto SETTING_DVDS_AUTORUN = "dvds.autorun"; + static constexpr auto SETTING_DVDS_PLAYERREGION = "dvds.playerregion"; + static constexpr auto SETTING_DVDS_AUTOMENU = "dvds.automenu"; + static constexpr auto SETTING_DISC_PLAYBACK = "disc.playback"; + static constexpr auto SETTING_BLURAY_PLAYERREGION = "bluray.playerregion"; + static constexpr auto SETTING_ACCESSIBILITY_AUDIOVISUAL = "accessibility.audiovisual"; + static constexpr auto SETTING_ACCESSIBILITY_AUDIOHEARING = "accessibility.audiohearing"; + static constexpr auto SETTING_ACCESSIBILITY_SUBHEARING = "accessibility.subhearing"; + static constexpr auto SETTING_SCRAPERS_MOVIESDEFAULT = "scrapers.moviesdefault"; + static constexpr auto SETTING_SCRAPERS_TVSHOWSDEFAULT = "scrapers.tvshowsdefault"; + static constexpr auto SETTING_SCRAPERS_MUSICVIDEOSDEFAULT = "scrapers.musicvideosdefault"; + static constexpr auto SETTING_PVRMANAGER_PRESELECTPLAYINGCHANNEL = + "pvrmanager.preselectplayingchannel"; + static constexpr auto SETTING_PVRMANAGER_SYNCCHANNELGROUPS = "pvrmanager.syncchannelgroups"; + static constexpr auto SETTING_PVRMANAGER_BACKENDCHANNELORDER = "pvrmanager.backendchannelorder"; + static constexpr auto SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERS = + "pvrmanager.usebackendchannelnumbers"; + static constexpr auto SETTING_PVRMANAGER_USEBACKENDCHANNELNUMBERSALWAYS = + "pvrmanager.usebackendchannelnumbersalways"; + static constexpr auto SETTING_PVRMANAGER_STARTGROUPCHANNELNUMBERSFROMONE = + "pvrmanager.startgroupchannelnumbersfromone"; + static constexpr auto SETTING_PVRMANAGER_CLIENTPRIORITIES = "pvrmanager.clientpriorities"; + static constexpr auto SETTING_PVRMANAGER_CHANNELMANAGER = "pvrmanager.channelmanager"; + static constexpr auto SETTING_PVRMANAGER_GROUPMANAGER = "pvrmanager.groupmanager"; + static constexpr auto SETTING_PVRMANAGER_CHANNELSCAN = "pvrmanager.channelscan"; + static constexpr auto SETTING_PVRMANAGER_RESETDB = "pvrmanager.resetdb"; + static constexpr auto SETTING_PVRMANAGER_ADDONS = "pvrmanager.addons"; + static constexpr auto SETTING_PVRMENU_DISPLAYCHANNELINFO = "pvrmenu.displaychannelinfo"; + static constexpr auto SETTING_PVRMENU_CLOSECHANNELOSDONSWITCH = "pvrmenu.closechannelosdonswitch"; + static constexpr auto SETTING_PVRMENU_ICONPATH = "pvrmenu.iconpath"; + static constexpr auto SETTING_PVRMENU_SEARCHICONS = "pvrmenu.searchicons"; + static constexpr auto SETTING_EPG_PAST_DAYSTODISPLAY = "epg.pastdaystodisplay"; + static constexpr auto SETTING_EPG_FUTURE_DAYSTODISPLAY = "epg.futuredaystodisplay"; + static constexpr auto SETTING_EPG_SELECTACTION = "epg.selectaction"; + static constexpr auto SETTING_EPG_HIDENOINFOAVAILABLE = "epg.hidenoinfoavailable"; + static constexpr auto SETTING_EPG_EPGUPDATE = "epg.epgupdate"; + static constexpr auto SETTING_EPG_PREVENTUPDATESWHILEPLAYINGTV = + "epg.preventupdateswhileplayingtv"; + static constexpr auto SETTING_EPG_RESETEPG = "epg.resetepg"; + static constexpr auto SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES = + "pvrplayback.switchtofullscreenchanneltypes"; + static constexpr auto SETTING_PVRPLAYBACK_SIGNALQUALITY = "pvrplayback.signalquality"; + static constexpr auto SETTING_PVRPLAYBACK_CONFIRMCHANNELSWITCH = + "pvrplayback.confirmchannelswitch"; + static constexpr auto SETTING_PVRPLAYBACK_CHANNELENTRYTIMEOUT = "pvrplayback.channelentrytimeout"; + static constexpr auto SETTING_PVRPLAYBACK_DELAYMARKLASTWATCHED = + "pvrplayback.delaymarklastwatched"; + static constexpr auto SETTING_PVRPLAYBACK_FPS = "pvrplayback.fps"; + static constexpr auto SETTING_PVRRECORD_INSTANTRECORDACTION = "pvrrecord.instantrecordaction"; + static constexpr auto SETTING_PVRRECORD_INSTANTRECORDTIME = "pvrrecord.instantrecordtime"; + static constexpr auto SETTING_PVRRECORD_MARGINSTART = "pvrrecord.marginstart"; + static constexpr auto SETTING_PVRRECORD_MARGINEND = "pvrrecord.marginend"; + static constexpr auto SETTING_PVRRECORD_TIMERNOTIFICATIONS = "pvrrecord.timernotifications"; + static constexpr auto SETTING_PVRRECORD_GROUPRECORDINGS = "pvrrecord.grouprecordings"; + static constexpr auto SETTING_PVRREMINDERS_AUTOCLOSEDELAY = "pvrreminders.autoclosedelay"; + static constexpr auto SETTING_PVRREMINDERS_AUTORECORD = "pvrreminders.autorecord"; + static constexpr auto SETTING_PVRREMINDERS_AUTOSWITCH = "pvrreminders.autoswitch"; + static constexpr auto SETTING_PVRPOWERMANAGEMENT_ENABLED = "pvrpowermanagement.enabled"; + static constexpr auto SETTING_PVRPOWERMANAGEMENT_BACKENDIDLETIME = + "pvrpowermanagement.backendidletime"; + static constexpr auto SETTING_PVRPOWERMANAGEMENT_SETWAKEUPCMD = "pvrpowermanagement.setwakeupcmd"; + static constexpr auto SETTING_PVRPOWERMANAGEMENT_PREWAKEUP = "pvrpowermanagement.prewakeup"; + static constexpr auto SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUP = "pvrpowermanagement.dailywakeup"; + static constexpr auto SETTING_PVRPOWERMANAGEMENT_DAILYWAKEUPTIME = + "pvrpowermanagement.dailywakeuptime"; + static constexpr auto SETTING_PVRPARENTAL_ENABLED = "pvrparental.enabled"; + static constexpr auto SETTING_PVRPARENTAL_PIN = "pvrparental.pin"; + static constexpr auto SETTING_PVRPARENTAL_DURATION = "pvrparental.duration"; + static constexpr auto SETTING_PVRCLIENT_MENUHOOK = "pvrclient.menuhook"; + static constexpr auto SETTING_PVRTIMERS_HIDEDISABLEDTIMERS = "pvrtimers.hidedisabledtimers"; + static constexpr auto SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS = + "musiclibrary.showcompilationartists"; + static constexpr auto SETTING_MUSICLIBRARY_SHOWDISCS = "musiclibrary.showdiscs"; + static constexpr auto SETTING_MUSICLIBRARY_USEORIGINALDATE = "musiclibrary.useoriginaldate"; + static constexpr auto SETTING_MUSICLIBRARY_USEARTISTSORTNAME = "musiclibrary.useartistsortname"; + static constexpr auto SETTING_MUSICLIBRARY_DOWNLOADINFO = "musiclibrary.downloadinfo"; + static constexpr auto SETTING_MUSICLIBRARY_ARTISTSFOLDER = "musiclibrary.artistsfolder"; + static constexpr auto SETTING_MUSICLIBRARY_PREFERONLINEALBUMART = + "musiclibrary.preferonlinealbumart"; + static constexpr auto SETTING_MUSICLIBRARY_ARTWORKLEVEL = "musiclibrary.artworklevel"; + static constexpr auto SETTING_MUSICLIBRARY_USEALLLOCALART = "musiclibrary.usealllocalart"; + static constexpr auto SETTING_MUSICLIBRARY_USEALLREMOTEART = "musiclibrary.useallremoteart"; + static constexpr auto SETTING_MUSICLIBRARY_ARTISTART_WHITELIST = + "musiclibrary.artistartwhitelist"; + static constexpr auto SETTING_MUSICLIBRARY_ALBUMART_WHITELIST = "musiclibrary.albumartwhitelist"; + static constexpr auto SETTING_MUSICLIBRARY_MUSICTHUMBS = "musiclibrary.musicthumbs"; + static constexpr auto SETTING_MUSICLIBRARY_ARTSETTINGS_UPDATED = "musiclibrary.artsettings"; + static constexpr auto SETTING_MUSICLIBRARY_ALBUMSSCRAPER = "musiclibrary.albumsscraper"; + static constexpr auto SETTING_MUSICLIBRARY_ARTISTSSCRAPER = "musiclibrary.artistsscraper"; + static constexpr auto SETTING_MUSICLIBRARY_OVERRIDETAGS = "musiclibrary.overridetags"; + static constexpr auto SETTING_MUSICLIBRARY_SHOWALLITEMS = "musiclibrary.showallitems"; + static constexpr auto SETTING_MUSICLIBRARY_UPDATEONSTARTUP = "musiclibrary.updateonstartup"; + static constexpr auto SETTING_MUSICLIBRARY_BACKGROUNDUPDATE = "musiclibrary.backgroundupdate"; + static constexpr auto SETTING_MUSICLIBRARY_CLEANUP = "musiclibrary.cleanup"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT = "musiclibrary.export"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT_FILETYPE = "musiclibrary.exportfiletype"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT_FOLDER = "musiclibrary.exportfolder"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT_ITEMS = "musiclibrary.exportitems"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED = "musiclibrary.exportunscraped"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT_OVERWRITE = "musiclibrary.exportoverwrite"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT_ARTWORK = "musiclibrary.exportartwork"; + static constexpr auto SETTING_MUSICLIBRARY_EXPORT_SKIPNFO = "musiclibrary.exportskipnfo"; + static constexpr auto SETTING_MUSICLIBRARY_IMPORT = "musiclibrary.import"; + static constexpr auto SETTING_MUSICPLAYER_AUTOPLAYNEXTITEM = "musicplayer.autoplaynextitem"; + static constexpr auto SETTING_MUSICPLAYER_QUEUEBYDEFAULT = "musicplayer.queuebydefault"; + static constexpr auto SETTING_MUSICPLAYER_SEEKSTEPS = "musicplayer.seeksteps"; + static constexpr auto SETTING_MUSICPLAYER_SEEKDELAY = "musicplayer.seekdelay"; + static constexpr auto SETTING_MUSICPLAYER_REPLAYGAINTYPE = "musicplayer.replaygaintype"; + static constexpr auto SETTING_MUSICPLAYER_REPLAYGAINPREAMP = "musicplayer.replaygainpreamp"; + static constexpr auto SETTING_MUSICPLAYER_REPLAYGAINNOGAINPREAMP = + "musicplayer.replaygainnogainpreamp"; + static constexpr auto SETTING_MUSICPLAYER_REPLAYGAINAVOIDCLIPPING = + "musicplayer.replaygainavoidclipping"; + static constexpr auto SETTING_MUSICPLAYER_CROSSFADE = "musicplayer.crossfade"; + static constexpr auto SETTING_MUSICPLAYER_CROSSFADEALBUMTRACKS = + "musicplayer.crossfadealbumtracks"; + static constexpr auto SETTING_MUSICPLAYER_VISUALISATION = "musicplayer.visualisation"; + static constexpr auto SETTING_MUSICFILES_SELECTACTION = "musicfiles.selectaction"; + static constexpr auto SETTING_MUSICFILES_USETAGS = "musicfiles.usetags"; + static constexpr auto SETTING_MUSICFILES_TRACKFORMAT = "musicfiles.trackformat"; + static constexpr auto SETTING_MUSICFILES_NOWPLAYINGTRACKFORMAT = + "musicfiles.nowplayingtrackformat"; + static constexpr auto SETTING_MUSICFILES_LIBRARYTRACKFORMAT = "musicfiles.librarytrackformat"; + static constexpr auto SETTING_MUSICFILES_FINDREMOTETHUMBS = "musicfiles.findremotethumbs"; + static constexpr auto SETTING_AUDIOCDS_AUTOACTION = "audiocds.autoaction"; + static constexpr auto SETTING_AUDIOCDS_USECDDB = "audiocds.usecddb"; + static constexpr auto SETTING_AUDIOCDS_RECORDINGPATH = "audiocds.recordingpath"; + static constexpr auto SETTING_AUDIOCDS_TRACKPATHFORMAT = "audiocds.trackpathformat"; + static constexpr auto SETTING_AUDIOCDS_ENCODER = "audiocds.encoder"; + static constexpr auto SETTING_AUDIOCDS_SETTINGS = "audiocds.settings"; + static constexpr auto SETTING_AUDIOCDS_EJECTONRIP = "audiocds.ejectonrip"; + static constexpr auto SETTING_MYMUSIC_SONGTHUMBINVIS = "mymusic.songthumbinvis"; + static constexpr auto SETTING_MYMUSIC_DEFAULTLIBVIEW = "mymusic.defaultlibview"; + static constexpr auto SETTING_PICTURES_USETAGS = "pictures.usetags"; + static constexpr auto SETTING_PICTURES_GENERATETHUMBS = "pictures.generatethumbs"; + static constexpr auto SETTING_PICTURES_SHOWVIDEOS = "pictures.showvideos"; + static constexpr auto SETTING_PICTURES_DISPLAYRESOLUTION = "pictures.displayresolution"; + static constexpr auto SETTING_SLIDESHOW_STAYTIME = "slideshow.staytime"; + static constexpr auto SETTING_SLIDESHOW_DISPLAYEFFECTS = "slideshow.displayeffects"; + static constexpr auto SETTING_SLIDESHOW_SHUFFLE = "slideshow.shuffle"; + static constexpr auto SETTING_SLIDESHOW_HIGHQUALITYDOWNSCALING = + "slideshow.highqualitydownscaling"; + static constexpr auto SETTING_WEATHER_CURRENTLOCATION = "weather.currentlocation"; + static constexpr auto SETTING_WEATHER_ADDON = "weather.addon"; + static constexpr auto SETTING_WEATHER_ADDONSETTINGS = "weather.addonsettings"; + static constexpr auto SETTING_SERVICES_DEVICENAME = "services.devicename"; + static constexpr auto SETTING_SERVICES_DEVICEUUID = "services.deviceuuid"; + static constexpr auto SETTING_SERVICES_UPNP = "services.upnp"; + static constexpr auto SETTING_SERVICES_UPNPSERVER = "services.upnpserver"; + static constexpr auto SETTING_SERVICES_UPNPANNOUNCE = "services.upnpannounce"; + static constexpr auto SETTING_SERVICES_UPNPLOOKFOREXTERNALSUBTITLES = + "services.upnplookforexternalsubtitles"; + static constexpr auto SETTING_SERVICES_UPNPCONTROLLER = "services.upnpcontroller"; + static constexpr auto SETTING_SERVICES_UPNPRENDERER = "services.upnprenderer"; + static constexpr auto SETTING_SERVICES_WEBSERVER = "services.webserver"; + static constexpr auto SETTING_SERVICES_WEBSERVERPORT = "services.webserverport"; + static constexpr auto SETTING_SERVICES_WEBSERVERAUTHENTICATION = + "services.webserverauthentication"; + static constexpr auto SETTING_SERVICES_WEBSERVERUSERNAME = "services.webserverusername"; + static constexpr auto SETTING_SERVICES_WEBSERVERPASSWORD = "services.webserverpassword"; + static constexpr auto SETTING_SERVICES_WEBSERVERSSL = "services.webserverssl"; + static constexpr auto SETTING_SERVICES_WEBSKIN = "services.webskin"; + static constexpr auto SETTING_SERVICES_ESENABLED = "services.esenabled"; + static constexpr auto SETTING_SERVICES_ESPORT = "services.esport"; + static constexpr auto SETTING_SERVICES_ESPORTRANGE = "services.esportrange"; + static constexpr auto SETTING_SERVICES_ESMAXCLIENTS = "services.esmaxclients"; + static constexpr auto SETTING_SERVICES_ESALLINTERFACES = "services.esallinterfaces"; + static constexpr auto SETTING_SERVICES_ESINITIALDELAY = "services.esinitialdelay"; + static constexpr auto SETTING_SERVICES_ESCONTINUOUSDELAY = "services.escontinuousdelay"; + static constexpr auto SETTING_SERVICES_ZEROCONF = "services.zeroconf"; + static constexpr auto SETTING_SERVICES_AIRPLAY = "services.airplay"; + static constexpr auto SETTING_SERVICES_AIRPLAYVOLUMECONTROL = "services.airplayvolumecontrol"; + static constexpr auto SETTING_SERVICES_USEAIRPLAYPASSWORD = "services.useairplaypassword"; + static constexpr auto SETTING_SERVICES_AIRPLAYPASSWORD = "services.airplaypassword"; + static constexpr auto SETTING_SERVICES_AIRPLAYVIDEOSUPPORT = "services.airplayvideosupport"; + static constexpr auto SETTING_SMB_WINSSERVER = "smb.winsserver"; + static constexpr auto SETTING_SMB_WORKGROUP = "smb.workgroup"; + static constexpr auto SETTING_SMB_MINPROTOCOL = "smb.minprotocol"; + static constexpr auto SETTING_SMB_MAXPROTOCOL = "smb.maxprotocol"; + static constexpr auto SETTING_SMB_LEGACYSECURITY = "smb.legacysecurity"; + static constexpr auto SETTING_SERVICES_WSDISCOVERY = "services.wsdiscovery"; + static constexpr auto SETTING_VIDEOSCREEN_MONITOR = "videoscreen.monitor"; + static constexpr auto SETTING_VIDEOSCREEN_SCREEN = "videoscreen.screen"; + static constexpr auto SETTING_VIDEOSCREEN_WHITELIST = "videoscreen.whitelist"; + static constexpr auto SETTING_VIDEOSCREEN_RESOLUTION = "videoscreen.resolution"; + static constexpr auto SETTING_VIDEOSCREEN_SCREENMODE = "videoscreen.screenmode"; + static constexpr auto SETTING_VIDEOSCREEN_FAKEFULLSCREEN = "videoscreen.fakefullscreen"; + static constexpr auto SETTING_VIDEOSCREEN_BLANKDISPLAYS = "videoscreen.blankdisplays"; + static constexpr auto SETTING_VIDEOSCREEN_STEREOSCOPICMODE = "videoscreen.stereoscopicmode"; + static constexpr auto SETTING_VIDEOSCREEN_PREFEREDSTEREOSCOPICMODE = + "videoscreen.preferedstereoscopicmode"; + static constexpr auto SETTING_VIDEOSCREEN_NOOFBUFFERS = "videoscreen.noofbuffers"; + static constexpr auto SETTING_VIDEOSCREEN_3DLUT = "videoscreen.cms3dlut"; + static constexpr auto SETTING_VIDEOSCREEN_DISPLAYPROFILE = "videoscreen.displayprofile"; + static constexpr auto SETTING_VIDEOSCREEN_GUICALIBRATION = "videoscreen.guicalibration"; + static constexpr auto SETTING_VIDEOSCREEN_TESTPATTERN = "videoscreen.testpattern"; + static constexpr auto SETTING_VIDEOSCREEN_LIMITEDRANGE = "videoscreen.limitedrange"; + static constexpr auto SETTING_VIDEOSCREEN_FRAMEPACKING = "videoscreen.framepacking"; + static constexpr auto SETTING_VIDEOSCREEN_10BITSURFACES = "videoscreen.10bitsurfaces"; + static constexpr auto SETTING_VIDEOSCREEN_GUISDRPEAKLUMINANCE = "videoscreen.guisdrpeakluminance"; + static constexpr auto SETTING_AUDIOOUTPUT_AUDIODEVICE = "audiooutput.audiodevice"; + static constexpr auto SETTING_AUDIOOUTPUT_CHANNELS = "audiooutput.channels"; + static constexpr auto SETTING_AUDIOOUTPUT_CONFIG = "audiooutput.config"; + static constexpr auto SETTING_AUDIOOUTPUT_SAMPLERATE = "audiooutput.samplerate"; + static constexpr auto SETTING_AUDIOOUTPUT_STEREOUPMIX = "audiooutput.stereoupmix"; + static constexpr auto SETTING_AUDIOOUTPUT_MAINTAINORIGINALVOLUME = + "audiooutput.maintainoriginalvolume"; + static constexpr auto SETTING_AUDIOOUTPUT_PROCESSQUALITY = "audiooutput.processquality"; + static constexpr auto SETTING_AUDIOOUTPUT_ATEMPOTHRESHOLD = "audiooutput.atempothreshold"; + static constexpr auto SETTING_AUDIOOUTPUT_STREAMSILENCE = "audiooutput.streamsilence"; + static constexpr auto SETTING_AUDIOOUTPUT_STREAMNOISE = "audiooutput.streamnoise"; + static constexpr auto SETTING_AUDIOOUTPUT_GUISOUNDMODE = "audiooutput.guisoundmode"; + static constexpr auto SETTING_AUDIOOUTPUT_GUISOUNDVOLUME = "audiooutput.guisoundvolume"; + static constexpr auto SETTING_AUDIOOUTPUT_PASSTHROUGH = "audiooutput.passthrough"; + static constexpr auto SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE = "audiooutput.passthroughdevice"; + static constexpr auto SETTING_AUDIOOUTPUT_AC3PASSTHROUGH = "audiooutput.ac3passthrough"; + static constexpr auto SETTING_AUDIOOUTPUT_AC3TRANSCODE = "audiooutput.ac3transcode"; + static constexpr auto SETTING_AUDIOOUTPUT_EAC3PASSTHROUGH = "audiooutput.eac3passthrough"; + static constexpr auto SETTING_AUDIOOUTPUT_DTSPASSTHROUGH = "audiooutput.dtspassthrough"; + static constexpr auto SETTING_AUDIOOUTPUT_TRUEHDPASSTHROUGH = "audiooutput.truehdpassthrough"; + static constexpr auto SETTING_AUDIOOUTPUT_DTSHDPASSTHROUGH = "audiooutput.dtshdpassthrough"; + static constexpr auto SETTING_AUDIOOUTPUT_DTSHDCOREFALLBACK = "audiooutput.dtshdcorefallback"; + static constexpr auto SETTING_AUDIOOUTPUT_VOLUMESTEPS = "audiooutput.volumesteps"; + static constexpr auto SETTING_INPUT_PERIPHERALS = "input.peripherals"; + static constexpr auto SETTING_INPUT_PERIPHERALLIBRARIES = "input.peripherallibraries"; + static constexpr auto SETTING_INPUT_ENABLEMOUSE = "input.enablemouse"; + static constexpr auto SETTING_INPUT_ASKNEWCONTROLLERS = "input.asknewcontrollers"; + static constexpr auto SETTING_INPUT_CONTROLLERCONFIG = "input.controllerconfig"; + static constexpr auto SETTING_INPUT_RUMBLENOTIFY = "input.rumblenotify"; + static constexpr auto SETTING_INPUT_TESTRUMBLE = "input.testrumble"; + static constexpr auto SETTING_INPUT_CONTROLLERPOWEROFF = "input.controllerpoweroff"; + static constexpr auto SETTING_INPUT_APPLEREMOTEMODE = "input.appleremotemode"; + static constexpr auto SETTING_INPUT_APPLEREMOTEALWAYSON = "input.appleremotealwayson"; + static constexpr auto SETTING_INPUT_APPLEREMOTESEQUENCETIME = "input.appleremotesequencetime"; + static constexpr auto SETTING_INPUT_SIRIREMOTEIDLETIMERENABLED = "input.siriremoteidletimerenabled"; + static constexpr auto SETTING_INPUT_SIRIREMOTEIDLETIME = "input.siriremoteidletime"; + static constexpr auto SETTING_INPUT_SIRIREMOTEHORIZONTALSENSITIVITY = + "input.siriremotehorizontalsensitivity"; + static constexpr auto SETTING_INPUT_SIRIREMOTEVERTICALSENSITIVITY = + "input.siriremoteverticalsensitivity"; + static constexpr auto SETTING_INPUT_TVOSUSEKODIKEYBOARD = "input.tvosusekodikeyboard"; + static constexpr auto SETTING_NETWORK_USEHTTPPROXY = "network.usehttpproxy"; + static constexpr auto SETTING_NETWORK_HTTPPROXYTYPE = "network.httpproxytype"; + static constexpr auto SETTING_NETWORK_HTTPPROXYSERVER = "network.httpproxyserver"; + static constexpr auto SETTING_NETWORK_HTTPPROXYPORT = "network.httpproxyport"; + static constexpr auto SETTING_NETWORK_HTTPPROXYUSERNAME = "network.httpproxyusername"; + static constexpr auto SETTING_NETWORK_HTTPPROXYPASSWORD = "network.httpproxypassword"; + static constexpr auto SETTING_NETWORK_BANDWIDTH = "network.bandwidth"; + static constexpr auto SETTING_POWERMANAGEMENT_DISPLAYSOFF = "powermanagement.displaysoff"; + static constexpr auto SETTING_POWERMANAGEMENT_SHUTDOWNTIME = "powermanagement.shutdowntime"; + static constexpr auto SETTING_POWERMANAGEMENT_SHUTDOWNSTATE = "powermanagement.shutdownstate"; + static constexpr auto SETTING_POWERMANAGEMENT_WAKEONACCESS = "powermanagement.wakeonaccess"; + static constexpr auto SETTING_POWERMANAGEMENT_WAITFORNETWORK = "powermanagement.waitfornetwork"; + static constexpr auto SETTING_DEBUG_SHOWLOGINFO = "debug.showloginfo"; + static constexpr auto SETTING_DEBUG_EXTRALOGGING = "debug.extralogging"; + static constexpr auto SETTING_DEBUG_SETEXTRALOGLEVEL = "debug.setextraloglevel"; + static constexpr auto SETTING_DEBUG_SCREENSHOTPATH = "debug.screenshotpath"; + static constexpr auto SETTING_DEBUG_SHARE_LOG = "debug.sharelog"; + static constexpr auto SETTING_EVENTLOG_ENABLED = "eventlog.enabled"; + static constexpr auto SETTING_EVENTLOG_ENABLED_NOTIFICATIONS = "eventlog.enablednotifications"; + static constexpr auto SETTING_EVENTLOG_SHOW = "eventlog.show"; + static constexpr auto SETTING_MASTERLOCK_LOCKCODE = "masterlock.lockcode"; + static constexpr auto SETTING_MASTERLOCK_STARTUPLOCK = "masterlock.startuplock"; + static constexpr auto SETTING_MASTERLOCK_MAXRETRIES = "masterlock.maxretries"; + static constexpr auto SETTING_CACHE_HARDDISK = "cache.harddisk"; + static constexpr auto SETTING_CACHEVIDEO_DVDROM = "cachevideo.dvdrom"; + static constexpr auto SETTING_CACHEVIDEO_LAN = "cachevideo.lan"; + static constexpr auto SETTING_CACHEVIDEO_INTERNET = "cachevideo.internet"; + static constexpr auto SETTING_CACHEAUDIO_DVDROM = "cacheaudio.dvdrom"; + static constexpr auto SETTING_CACHEAUDIO_LAN = "cacheaudio.lan"; + static constexpr auto SETTING_CACHEAUDIO_INTERNET = "cacheaudio.internet"; + static constexpr auto SETTING_CACHEDVD_DVDROM = "cachedvd.dvdrom"; + static constexpr auto SETTING_CACHEDVD_LAN = "cachedvd.lan"; + static constexpr auto SETTING_CACHEUNKNOWN_INTERNET = "cacheunknown.internet"; + static constexpr auto SETTING_SYSTEM_PLAYLISTSPATH = "system.playlistspath"; + static constexpr auto SETTING_ADDONS_AUTOUPDATES = "general.addonupdates"; + static constexpr auto SETTING_ADDONS_NOTIFICATIONS = "general.addonnotifications"; + static constexpr auto SETTING_ADDONS_SHOW_RUNNING = "addons.showrunning"; + static constexpr auto SETTING_ADDONS_ALLOW_UNKNOWN_SOURCES = "addons.unknownsources"; + static constexpr auto SETTING_ADDONS_UPDATEMODE = "addons.updatemode"; + static constexpr auto SETTING_ADDONS_MANAGE_DEPENDENCIES = "addons.managedependencies"; + static constexpr auto SETTING_ADDONS_REMOVE_ORPHANED_DEPENDENCIES = + "addons.removeorphaneddependencies"; + static constexpr auto SETTING_GENERAL_ADDONFOREIGNFILTER = "general.addonforeignfilter"; + static constexpr auto SETTING_GENERAL_ADDONBROKENFILTER = "general.addonbrokenfilter"; + static constexpr auto SETTING_SOURCE_VIDEOS = "source.videos"; + static constexpr auto SETTING_SOURCE_MUSIC = "source.music"; + static constexpr auto SETTING_SOURCE_PICTURES = "source.pictures"; + + // values for SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS + static const int VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_MOVIES = 0; + static const int VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_TVSHOWEPISODES = 1; + static const int VIDEOLIBRARY_THUMB_SHOW_UNWATCHED_EPISODE = 2; + // values for SETTING_VIDEOLIBRARY_ARTWORK_LEVEL + static const int VIDEOLIBRARY_ARTWORK_LEVEL_ALL = 0; + static const int VIDEOLIBRARY_ARTWORK_LEVEL_BASIC = 1; + static const int VIDEOLIBRARY_ARTWORK_LEVEL_CUSTOM = 2; + static const int VIDEOLIBRARY_ARTWORK_LEVEL_NONE = 3; + + // values for SETTING_MUSICLIBRARY_ARTWORKLEVEL + static const int MUSICLIBRARY_ARTWORK_LEVEL_ALL = 0; + static const int MUSICLIBRARY_ARTWORK_LEVEL_BASIC = 1; + static const int MUSICLIBRARY_ARTWORK_LEVEL_CUSTOM = 2; + static const int MUSICLIBRARY_ARTWORK_LEVEL_NONE = 3; + + /*! + \brief Creates a new settings wrapper around a new settings manager. + + For access to the "global" settings wrapper the static GetInstance() method should + be used. + */ + CSettings() = default; + ~CSettings() override = default; + + CSettingsManager* GetSettingsManager() const { return m_settingsManager; } + + // specialization of CSettingsBase + bool Initialize() override; + + /*! + \brief Registers the given ISubSettings implementation. + + \param subSettings ISubSettings implementation + */ + void RegisterSubSettings(ISubSettings* subSettings); + /*! + \brief Unregisters the given ISubSettings implementation. + + \param subSettings ISubSettings implementation + */ + void UnregisterSubSettings(ISubSettings* subSettings); + + // implementations of CSettingsBase + bool Load() override; + bool Save() override; + + /*! + \brief Loads setting values from the given (XML) file. + + \param file Path to an XML file containing setting values + \return True if the setting values were successfully loaded, false otherwise + */ + bool Load(const std::string &file); + /*! + \brief Loads setting values from the given XML element. + + \param root XML element containing setting values + \return True if the setting values were successfully loaded, false otherwise + */ + bool Load(const TiXmlElement* root); + /*! + \brief Loads setting values from the given XML element. + + \param root XML element containing setting values + \param hide Whether to hide the loaded settings or not + \return True if the setting values were successfully loaded, false otherwise + */ + bool LoadHidden(const TiXmlElement *root) { return CSettingsBase::LoadHiddenValuesFromXml(root); } + + /*! + \brief Saves the setting values to the given (XML) file. + + \param file Path to an XML file + \return True if the setting values were successfully saved, false otherwise + */ + bool Save(const std::string &file); + /*! + \brief Saves the setting values to the given XML node. + + \param root XML node + \return True if the setting values were successfully saved, false otherwise + */ + bool Save(TiXmlNode* root) const override; + + /*! + \brief Loads the setting being represented by the given XML node with the + given identifier. + + \param node XML node representing the setting to load + \param settingId Setting identifier + \return True if the setting was successfully loaded from the given XML node, false otherwise + */ + bool LoadSetting(const TiXmlNode *node, const std::string &settingId); + + // overwrite (not override) from CSettingsBase + bool GetBool(const std::string& id) const; + + /*! + \brief Clears the complete settings. + + This removes all initialized settings, groups, categories and sections and + returns to the uninitialized state. Any registered callbacks or + implementations stay registered. + */ + void Clear() override; + +protected: + // specializations of CSettingsBase + void InitializeSettingTypes() override; + void InitializeControls() override; + void InitializeOptionFillers() override; + void UninitializeOptionFillers() override; + void InitializeConditions() override; + void UninitializeConditions() override; + void InitializeVisibility() override; + void InitializeDefaults() override; + void InitializeISettingsHandlers() override; + void UninitializeISettingsHandlers() override; + void InitializeISubSettings() override; + void UninitializeISubSettings() override; + void InitializeISettingCallbacks() override; + void UninitializeISettingCallbacks() override; + + // implementation of CSettingsBase + bool InitializeDefinitions() override; + +private: + CSettings(const CSettings&) = delete; + CSettings const& operator=(CSettings const&) = delete; + + bool Load(const TiXmlElement* root, bool& updated); + + // implementation of ISubSettings + bool Load(const TiXmlNode* settings) override; + + bool Initialize(const std::string &file); + bool Reset(); + + std::set<ISubSettings*> m_subSettings; +}; diff --git a/xbmc/settings/SettingsBase.cpp b/xbmc/settings/SettingsBase.cpp new file mode 100644 index 0000000..a118a36 --- /dev/null +++ b/xbmc/settings/SettingsBase.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsBase.h" + +#include "settings/SettingUtils.h" +#include "settings/SettingsValueXmlSerializer.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingsManager.h" +#include "utils/Variant.h" +#include "utils/XBMCTinyXML.h" + +#include <mutex> + +#define SETTINGS_XML_ROOT "settings" + +CSettingsBase::CSettingsBase() + : m_settingsManager(new CSettingsManager()) +{ } + +CSettingsBase::~CSettingsBase() +{ + Uninitialize(); + + delete m_settingsManager; +} + +bool CSettingsBase::Initialize() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (m_initialized) + return false; + + // register custom setting types + InitializeSettingTypes(); + // register custom setting controls + InitializeControls(); + + // option fillers and conditions need to be + // initialized before the setting definitions + InitializeOptionFillers(); + InitializeConditions(); + + // load the settings definitions + if (!InitializeDefinitions()) + return false; + + InitializeVisibility(); + InitializeDefaults(); + + m_settingsManager->SetInitialized(); + + InitializeISettingsHandlers(); + InitializeISubSettings(); + InitializeISettingCallbacks(); + + m_initialized = true; + + return true; +} + +bool CSettingsBase::IsInitialized() const +{ + return m_initialized && m_settingsManager->IsInitialized(); +} + +bool CSettingsBase::LoadValuesFromXml(const CXBMCTinyXML& xml, bool& updated) +{ + const TiXmlElement* xmlRoot = xml.RootElement(); + if (xmlRoot == nullptr || xmlRoot->ValueStr() != SETTINGS_XML_ROOT) + return false; + + return m_settingsManager->Load(xmlRoot, updated); +} + +bool CSettingsBase::LoadValuesFromXml(const TiXmlElement* root, bool& updated) +{ + if (root == nullptr) + return false; + + return m_settingsManager->Load(root, updated); +} + +bool CSettingsBase::LoadHiddenValuesFromXml(const TiXmlElement* root) +{ + if (root == nullptr) + return false; + + std::map<std::string, std::shared_ptr<CSetting>> loadedSettings; + + bool updated; + // don't trigger events for hidden settings + bool success = m_settingsManager->Load(root, updated, false, &loadedSettings); + if (success) + { + for(std::map<std::string, std::shared_ptr<CSetting>>::const_iterator setting = loadedSettings.begin(); setting != loadedSettings.end(); ++setting) + setting->second->SetVisible(false); + } + + return success; +} + +void CSettingsBase::SetLoaded() +{ + m_settingsManager->SetLoaded(); +} + +bool CSettingsBase::IsLoaded() const +{ + return m_settingsManager->IsLoaded(); +} + +bool CSettingsBase::SaveValuesToXml(CXBMCTinyXML& xml) const +{ + std::string serializedSettings; + auto xmlSerializer = std::make_unique<CSettingsValueXmlSerializer>(); + if (!m_settingsManager->Save(xmlSerializer.get(), serializedSettings)) + return false; + + if (!xml.Parse(serializedSettings)) + return false; + + return true; +} + +void CSettingsBase::Unload() +{ + m_settingsManager->Unload(); +} + +void CSettingsBase::Uninitialize() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (!m_initialized) + return; + + // unregister setting option fillers + UninitializeOptionFillers(); + + // unregister setting conditions + UninitializeConditions(); + + // unregister ISettingCallback implementations + UninitializeISettingCallbacks(); + + // cleanup the settings manager + m_settingsManager->Clear(); + + // unregister ISubSettings implementations + UninitializeISubSettings(); + // unregister ISettingsHandler implementations + UninitializeISettingsHandlers(); + + m_initialized = false; +} + +void CSettingsBase::RegisterCallback(ISettingCallback* callback, const std::set<std::string>& settingList) +{ + m_settingsManager->RegisterCallback(callback, settingList); +} + +void CSettingsBase::UnregisterCallback(ISettingCallback* callback) +{ + m_settingsManager->UnregisterCallback(callback); +} + +SettingPtr CSettingsBase::GetSetting(const std::string& id) const +{ + if (id.empty()) + return nullptr; + + return m_settingsManager->GetSetting(id); +} + +std::vector<std::shared_ptr<CSettingSection>> CSettingsBase::GetSections() const +{ + return m_settingsManager->GetSections(); +} + +std::shared_ptr<CSettingSection> CSettingsBase::GetSection(const std::string& section) const +{ + if (section.empty()) + return nullptr; + + return m_settingsManager->GetSection(section); +} + +bool CSettingsBase::GetBool(const std::string& id) const +{ + return m_settingsManager->GetBool(id); +} + +bool CSettingsBase::SetBool(const std::string& id, bool value) +{ + return m_settingsManager->SetBool(id, value); +} + +bool CSettingsBase::ToggleBool(const std::string& id) +{ + return m_settingsManager->ToggleBool(id); +} + +int CSettingsBase::GetInt(const std::string& id) const +{ + return m_settingsManager->GetInt(id); +} + +bool CSettingsBase::SetInt(const std::string& id, int value) +{ + return m_settingsManager->SetInt(id, value); +} + +double CSettingsBase::GetNumber(const std::string& id) const +{ + return m_settingsManager->GetNumber(id); +} + +bool CSettingsBase::SetNumber(const std::string& id, double value) +{ + return m_settingsManager->SetNumber(id, value); +} + +std::string CSettingsBase::GetString(const std::string& id) const +{ + return m_settingsManager->GetString(id); +} + +bool CSettingsBase::SetString(const std::string& id, const std::string& value) +{ + return m_settingsManager->SetString(id, value); +} + +std::vector<CVariant> CSettingsBase::GetList(const std::string& id) const +{ + std::shared_ptr<CSetting> setting = m_settingsManager->GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::List) + return std::vector<CVariant>(); + + return CSettingUtils::GetList(std::static_pointer_cast<CSettingList>(setting)); +} + +bool CSettingsBase::SetList(const std::string& id, const std::vector<CVariant>& value) +{ + std::shared_ptr<CSetting> setting = m_settingsManager->GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::List) + return false; + + return CSettingUtils::SetList(std::static_pointer_cast<CSettingList>(setting), value); +} + +bool CSettingsBase::SetDefault(const std::string &id) +{ + return m_settingsManager->SetDefault(id); +} + +void CSettingsBase::SetDefaults() +{ + m_settingsManager->SetDefaults(); +} + +bool CSettingsBase::InitializeDefinitionsFromXml(const CXBMCTinyXML& xml) +{ + const TiXmlElement* root = xml.RootElement(); + if (root == nullptr) + return false; + + return m_settingsManager->Initialize(root); +} diff --git a/xbmc/settings/SettingsBase.h b/xbmc/settings/SettingsBase.h new file mode 100644 index 0000000..209b6cc --- /dev/null +++ b/xbmc/settings/SettingsBase.h @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingCallback.h" +#include "threads/CriticalSection.h" + +#include <set> +#include <string> +#include <vector> + +class CSetting; +class CSettingSection; +class CSettingsManager; +class CVariant; +class CXBMCTinyXML; +class TiXmlElement; + +/*! + \brief Basic wrapper around CSettingsManager providing the framework for + properly setting up the settings manager and registering all the callbacks, + handlers and custom setting types. + \sa CSettingsManager + */ +class CSettingsBase +{ +public: + virtual ~CSettingsBase(); + + CSettingsManager* GetSettingsManager() const { return m_settingsManager; } + + /*! + \brief Initializes the setting system with the generic + settings definition and platform specific setting definitions. + + \return True if the initialization was successful, false otherwise + */ + virtual bool Initialize(); + /*! + \brief Returns whether the settings system has been initialized or not. + */ + virtual bool IsInitialized() const; + /*! + \brief Loads the setting values. + + \return True if the setting values are successfully loaded, false otherwise + */ + virtual bool Load() = 0; + /*! + \brief Tells the settings system that all setting values + have been loaded. + + This manual trigger is necessary to enable the ISettingCallback methods + being executed. + */ + virtual void SetLoaded(); + /*! + \brief Returns whether the settings system has been loaded or not. + */ + virtual bool IsLoaded() const; + /*! + \brief Saves the setting values. + + \return True if the setting values were successfully saved, false otherwise + */ + virtual bool Save() = 0; + /*! + \brief Unloads the previously loaded setting values. + + The values of all the settings are reset to their default values. + */ + virtual void Unload(); + /*! + \brief Uninitializes the settings system. + + Unregisters all previously registered callbacks and destroys all setting + objects. + */ + virtual void Uninitialize(); + + /*! + \brief Registers the given ISettingCallback implementation for the given + set of settings. + + \param callback ISettingCallback implementation + \param settingList List of setting identifiers for which the given callback shall be triggered + */ + void RegisterCallback(ISettingCallback* callback, const std::set<std::string>& settingList); + /*! + \brief Unregisters the given ISettingCallback implementation. + + \param callback ISettingCallback implementation + */ + void UnregisterCallback(ISettingCallback* callback); + + /*! + \brief Gets the setting with the given identifier. + + \param id Setting identifier + \return Setting object with the given identifier or NULL if the identifier is unknown + */ + std::shared_ptr<CSetting> GetSetting(const std::string& id) const; + /*! + \brief Gets the full list of setting sections. + + \return List of setting sections + */ + std::vector<std::shared_ptr<CSettingSection>> GetSections() const; + /*! + \brief Gets the setting section with the given identifier. + + \param section Setting section identifier + \return Setting section with the given identifier or NULL if the identifier is unknown + */ + std::shared_ptr<CSettingSection> GetSection(const std::string& section) const; + + /*! + \brief Gets the boolean value of the setting with the given identifier. + + \param id Setting identifier + \return Boolean value of the setting with the given identifier + */ + bool GetBool(const std::string& id) const; + /*! + \brief Gets the integer value of the setting with the given identifier. + + \param id Setting identifier + \return Integer value of the setting with the given identifier + */ + int GetInt(const std::string& id) const; + /*! + \brief Gets the real number value of the setting with the given identifier. + + \param id Setting identifier + \return Real number value of the setting with the given identifier + */ + double GetNumber(const std::string& id) const; + /*! + \brief Gets the string value of the setting with the given identifier. + + \param id Setting identifier + \return String value of the setting with the given identifier + */ + std::string GetString(const std::string& id) const; + /*! + \brief Gets the values of the list setting with the given identifier. + + \param id Setting identifier + \return List of values of the setting with the given identifier + */ + std::vector<CVariant> GetList(const std::string& id) const; + + /*! + \brief Sets the boolean value of the setting with the given identifier. + + \param id Setting identifier + \param value Boolean value to set + \return True if setting the value was successful, false otherwise + */ + bool SetBool(const std::string& id, bool value); + /*! + \brief Toggles the boolean value of the setting with the given identifier. + + \param id Setting identifier + \return True if toggling the boolean value was successful, false otherwise + */ + bool ToggleBool(const std::string& id); + /*! + \brief Sets the integer value of the setting with the given identifier. + + \param id Setting identifier + \param value Integer value to set + \return True if setting the value was successful, false otherwise + */ + bool SetInt(const std::string& id, int value); + /*! + \brief Sets the real number value of the setting with the given identifier. + + \param id Setting identifier + \param value Real number value to set + \return True if setting the value was successful, false otherwise + */ + bool SetNumber(const std::string& id, double value); + /*! + \brief Sets the string value of the setting with the given identifier. + + \param id Setting identifier + \param value String value to set + \return True if setting the value was successful, false otherwise + */ + bool SetString(const std::string& id, const std::string& value); + /*! + \brief Sets the values of the list setting with the given identifier. + + \param id Setting identifier + \param value Values to set + \return True if setting the values was successful, false otherwise + */ + bool SetList(const std::string& id, const std::vector<CVariant>& value); + + /*! + \brief Sets the value of the setting with the given identifier to its default. + + \param id Setting identifier + \return True if setting the value to its default was successful, false otherwise + */ + bool SetDefault(const std::string &id); + /*! + \brief Sets the value of all settings to their default. + */ + void SetDefaults(); + +protected: + CSettingsBase(); + + virtual void InitializeSettingTypes() { } + virtual void InitializeControls() { } + virtual void InitializeOptionFillers() { } + virtual void UninitializeOptionFillers() { } + virtual void InitializeConditions() { } + virtual void UninitializeConditions() { } + virtual bool InitializeDefinitions() = 0; + virtual void InitializeVisibility() { } + virtual void InitializeDefaults() { } + virtual void InitializeISettingsHandlers() { } + virtual void UninitializeISettingsHandlers() { } + virtual void InitializeISubSettings() { } + virtual void UninitializeISubSettings() { } + virtual void InitializeISettingCallbacks() { } + virtual void UninitializeISettingCallbacks() { } + + bool InitializeDefinitionsFromXml(const CXBMCTinyXML& xml); + + /*! + \brief Loads setting values from the given document in XML format + + \param xml Document in XML format from which the settings are loaded + \param updated Output parameter indicating whether setting values had to be updated + \return True if the setting values were successfully loaded, false otherwise + */ + bool LoadValuesFromXml(const CXBMCTinyXML& xml, bool& updated); + /*! + \brief Saves the setting values in XML format to the given document. + + \param xml Document to save the setting values in XML format into + \return True if the setting values were successfully saved, false otherwise + */ + bool SaveValuesToXml(CXBMCTinyXML& xml) const; + + /*! + \brief Loads setting values from the given XML element. + + \param root XML element containing setting values + \param updated Output parameter indicating whether setting values had to be updated + \return True if the setting values were successfully loaded, false otherwise + */ + bool LoadValuesFromXml(const TiXmlElement* root, bool& updated); + + /*! + \brief Loads hidden setting values from the given XML element. + + \param root XML element containing setting values + \return True if the setting values were successfully loaded, false otherwise + */ + bool LoadHiddenValuesFromXml(const TiXmlElement* root); + + bool m_initialized = false; + CSettingsManager* m_settingsManager; + mutable CCriticalSection m_critical; +private: + CSettingsBase(const CSettingsBase&) = delete; + CSettingsBase& operator=(const CSettingsBase&) = delete; +}; diff --git a/xbmc/settings/SettingsComponent.cpp b/xbmc/settings/SettingsComponent.cpp new file mode 100644 index 0000000..0f5f7ae --- /dev/null +++ b/xbmc/settings/SettingsComponent.cpp @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsComponent.h" + +#include "CompileInfo.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "application/AppParams.h" +#include "filesystem/Directory.h" +#include "filesystem/SpecialProtocol.h" +#ifdef TARGET_DARWIN_EMBEDDED +#include "platform/darwin/ios-common/DarwinEmbedUtils.h" +#endif +#ifdef TARGET_WINDOWS +#include "platform/Environment.h" +#endif +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SubtitlesSettings.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#ifdef TARGET_WINDOWS +#include "win32util.h" +#endif + +CSettingsComponent::CSettingsComponent() + : m_settings(new CSettings()), + m_advancedSettings(new CAdvancedSettings()), + m_subtitlesSettings(new KODI::SUBTITLES::CSubtitlesSettings(m_settings)), + m_profileManager(new CProfileManager()) +{ +} + +CSettingsComponent::~CSettingsComponent() +{ +} + +void CSettingsComponent::Initialize() +{ + if (m_state == State::DEINITED) + { + const auto params = CServiceBroker::GetAppParams(); + + // only the InitDirectories* for the current platform should return true + bool inited = InitDirectoriesLinux(params->HasPlatformDirectories()); + if (!inited) + inited = InitDirectoriesOSX(params->HasPlatformDirectories()); + if (!inited) + inited = InitDirectoriesWin32(params->HasPlatformDirectories()); + + m_settings->Initialize(); + + m_advancedSettings->Initialize(*m_settings->GetSettingsManager()); + URIUtils::RegisterAdvancedSettings(*m_advancedSettings); + + m_profileManager->Initialize(m_settings); + + m_state = State::INITED; + } +} + +bool CSettingsComponent::Load() +{ + if (m_state == State::INITED) + { + if (!m_profileManager->Load()) + { + CLog::Log(LOGFATAL, "unable to load profile"); + return false; + } + + CSpecialProtocol::RegisterProfileManager(*m_profileManager); + XFILE::IDirectory::RegisterProfileManager(*m_profileManager); + + if (!m_settings->Load()) + { + CLog::Log(LOGFATAL, "unable to load settings"); + return false; + } + + m_settings->SetLoaded(); + + m_state = State::LOADED; + return true; + } + else if (m_state == State::LOADED) + { + return true; + } + else + { + return false; + } +} + +void CSettingsComponent::Deinitialize() +{ + if (m_state >= State::INITED) + { + if (m_state == State::LOADED) + { + m_subtitlesSettings.reset(); + + m_settings->Unload(); + + XFILE::IDirectory::UnregisterProfileManager(); + CSpecialProtocol::UnregisterProfileManager(); + } + m_profileManager->Uninitialize(); + + URIUtils::UnregisterAdvancedSettings(); + m_advancedSettings->Uninitialize(*m_settings->GetSettingsManager()); + + m_settings->Uninitialize(); + } + m_state = State::DEINITED; +} + +std::shared_ptr<CSettings> CSettingsComponent::GetSettings() +{ + return m_settings; +} + +std::shared_ptr<CAdvancedSettings> CSettingsComponent::GetAdvancedSettings() +{ + return m_advancedSettings; +} + +std::shared_ptr<KODI::SUBTITLES::CSubtitlesSettings> CSettingsComponent::GetSubtitlesSettings() +{ + return m_subtitlesSettings; +} + +std::shared_ptr<CProfileManager> CSettingsComponent::GetProfileManager() +{ + return m_profileManager; +} + +bool CSettingsComponent::InitDirectoriesLinux(bool bPlatformDirectories) +{ + /* + The following is the directory mapping for Platform Specific Mode: + + special://xbmc/ => [read-only] system directory (/usr/share/kodi) + special://home/ => [read-write] user's directory that will override special://kodi/ system-wide + installations like skins, screensavers, etc. + ($HOME/.kodi) + NOTE: XBMC will look in both special://xbmc/addons and special://home/addons for addons. + special://masterprofile/ => [read-write] userdata of master profile. It will by default be + mapped to special://home/userdata ($HOME/.kodi/userdata) + special://profile/ => [read-write] current profile's userdata directory. + Generally special://masterprofile for the master profile or + special://masterprofile/profiles/<profile_name> for other profiles. + + NOTE: All these root directories are lowercase. Some of the sub-directories + might be mixed case. + */ + +#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) + std::string appPath; + std::string appName = CCompileInfo::GetAppName(); + std::string dotLowerAppName = "." + appName; + StringUtils::ToLower(dotLowerAppName); + const char* envAppHome = "KODI_HOME"; + const char* envAppBinHome = "KODI_BIN_HOME"; + const char* envAppTemp = "KODI_TEMP"; + + std::string userHome; + if (getenv("KODI_DATA")) + userHome = getenv("KODI_DATA"); + else if (getenv("HOME")) + { + userHome = getenv("HOME"); + userHome.append("/" + dotLowerAppName); + } + else + { + userHome = "/root"; + userHome.append("/" + dotLowerAppName); + } + + std::string strTempPath; + if (getenv(envAppTemp)) + strTempPath = getenv(envAppTemp); + else + strTempPath = userHome + "/temp"; + + + std::string binaddonAltDir; + if (getenv("KODI_BINADDON_PATH")) + binaddonAltDir = getenv("KODI_BINADDON_PATH"); + + auto appBinPath = CUtil::GetHomePath(envAppBinHome); + // overridden by user + if (getenv(envAppHome)) + appPath = getenv(envAppHome); + else + { + // use build time default + appPath = INSTALL_PATH; + /* Check if binaries and arch independent data files are being kept in + * separate locations. */ + if (!XFILE::CDirectory::Exists(URIUtils::AddFileToFolder(appPath, "userdata"))) + { + /* Attempt to locate arch independent data files. */ + appPath = CUtil::GetHomePath(appBinPath); + if (!XFILE::CDirectory::Exists(URIUtils::AddFileToFolder(appPath, "userdata"))) + { + fprintf(stderr, "Unable to find path to %s data files!\n", appName.c_str()); + exit(1); + } + } + } + + /* Set some environment variables */ + setenv(envAppBinHome, appBinPath.c_str(), 0); + setenv(envAppHome, appPath.c_str(), 0); + + if (bPlatformDirectories) + { + // map our special drives + CSpecialProtocol::SetXBMCBinPath(appBinPath); + CSpecialProtocol::SetXBMCAltBinAddonPath(binaddonAltDir); + CSpecialProtocol::SetXBMCPath(appPath); + CSpecialProtocol::SetHomePath(userHome); + CSpecialProtocol::SetMasterProfilePath(userHome + "/userdata"); + CSpecialProtocol::SetTempPath(strTempPath); + CSpecialProtocol::SetLogPath(strTempPath); + + CreateUserDirs(); + + } + else + { + URIUtils::AddSlashAtEnd(appPath); + + CSpecialProtocol::SetXBMCBinPath(appBinPath); + CSpecialProtocol::SetXBMCAltBinAddonPath(binaddonAltDir); + CSpecialProtocol::SetXBMCPath(appPath); + CSpecialProtocol::SetHomePath(URIUtils::AddFileToFolder(appPath, "portable_data")); + CSpecialProtocol::SetMasterProfilePath(URIUtils::AddFileToFolder(appPath, "portable_data/userdata")); + + std::string strTempPath = appPath; + strTempPath = URIUtils::AddFileToFolder(strTempPath, "portable_data/temp"); + if (getenv(envAppTemp)) + strTempPath = getenv(envAppTemp); + CSpecialProtocol::SetTempPath(strTempPath); + CSpecialProtocol::SetLogPath(strTempPath); + CreateUserDirs(); + } + CSpecialProtocol::SetXBMCBinAddonPath(appBinPath + "/addons"); + + return true; +#else + return false; +#endif +} + +bool CSettingsComponent::InitDirectoriesOSX(bool bPlatformDirectories) +{ +#if defined(TARGET_DARWIN) + std::string userHome; + if (getenv("HOME")) + userHome = getenv("HOME"); + else + userHome = "/root"; + + std::string binaddonAltDir; + if (getenv("KODI_BINADDON_PATH")) + binaddonAltDir = getenv("KODI_BINADDON_PATH"); + + std::string appPath = CUtil::GetHomePath(); + setenv("KODI_HOME", appPath.c_str(), 0); + +#if defined(TARGET_DARWIN_EMBEDDED) + std::string fontconfigPath; + fontconfigPath = appPath + "/system/players/VideoPlayer/etc/fonts/fonts.conf"; + setenv("FONTCONFIG_FILE", fontconfigPath.c_str(), 0); +#endif + + // setup path to our internal dylibs so loader can find them + std::string frameworksPath = CUtil::GetFrameworksPath(); + CSpecialProtocol::SetXBMCFrameworksPath(frameworksPath); + + if (bPlatformDirectories) + { + // map our special drives + CSpecialProtocol::SetXBMCBinPath(appPath); + CSpecialProtocol::SetXBMCAltBinAddonPath(binaddonAltDir); + CSpecialProtocol::SetXBMCPath(appPath); +#if defined(TARGET_DARWIN_EMBEDDED) + std::string appName = CCompileInfo::GetAppName(); + CSpecialProtocol::SetHomePath(userHome + "/" + CDarwinEmbedUtils::GetAppRootFolder() + "/" + + appName); + CSpecialProtocol::SetMasterProfilePath(userHome + "/" + CDarwinEmbedUtils::GetAppRootFolder() + + "/" + appName + "/userdata"); +#else + std::string appName = CCompileInfo::GetAppName(); + CSpecialProtocol::SetHomePath(userHome + "/Library/Application Support/" + appName); + CSpecialProtocol::SetMasterProfilePath(userHome + "/Library/Application Support/" + appName + "/userdata"); +#endif + + std::string dotLowerAppName = "." + appName; + StringUtils::ToLower(dotLowerAppName); + // location for temp files +#if defined(TARGET_DARWIN_EMBEDDED) + std::string strTempPath = URIUtils::AddFileToFolder( + userHome, std::string(CDarwinEmbedUtils::GetAppRootFolder()) + "/" + appName + "/temp"); +#else + std::string strTempPath = URIUtils::AddFileToFolder(userHome, dotLowerAppName + "/"); + XFILE::CDirectory::Create(strTempPath); + strTempPath = URIUtils::AddFileToFolder(userHome, dotLowerAppName + "/temp"); +#endif + CSpecialProtocol::SetTempPath(strTempPath); + + // xbmc.log file location +#if defined(TARGET_DARWIN_EMBEDDED) + strTempPath = userHome + "/" + std::string(CDarwinEmbedUtils::GetAppRootFolder()); +#else + strTempPath = userHome + "/Library/Logs"; +#endif + CSpecialProtocol::SetLogPath(strTempPath); + CreateUserDirs(); + } + else + { + URIUtils::AddSlashAtEnd(appPath); + + CSpecialProtocol::SetXBMCBinPath(appPath); + CSpecialProtocol::SetXBMCAltBinAddonPath(binaddonAltDir); + CSpecialProtocol::SetXBMCPath(appPath); + CSpecialProtocol::SetHomePath(URIUtils::AddFileToFolder(appPath, "portable_data")); + CSpecialProtocol::SetMasterProfilePath(URIUtils::AddFileToFolder(appPath, "portable_data/userdata")); + + std::string strTempPath = URIUtils::AddFileToFolder(appPath, "portable_data/temp"); + CSpecialProtocol::SetTempPath(strTempPath); + CSpecialProtocol::SetLogPath(strTempPath); + CreateUserDirs(); + } + CSpecialProtocol::SetXBMCBinAddonPath(appPath + "/addons"); + return true; +#else + return false; +#endif +} + +bool CSettingsComponent::InitDirectoriesWin32(bool bPlatformDirectories) +{ +#ifdef TARGET_WINDOWS + std::string xbmcPath = CUtil::GetHomePath(); + CEnvironment::setenv("KODI_HOME", xbmcPath); + CSpecialProtocol::SetXBMCBinPath(xbmcPath); + CSpecialProtocol::SetXBMCPath(xbmcPath); + CSpecialProtocol::SetXBMCBinAddonPath(xbmcPath + "/addons"); + + std::string strWin32UserFolder = CWIN32Util::GetProfilePath(bPlatformDirectories); + CSpecialProtocol::SetLogPath(strWin32UserFolder); + CSpecialProtocol::SetHomePath(strWin32UserFolder); + CSpecialProtocol::SetMasterProfilePath(URIUtils::AddFileToFolder(strWin32UserFolder, "userdata")); + CSpecialProtocol::SetTempPath(URIUtils::AddFileToFolder(strWin32UserFolder,"cache")); + + CEnvironment::setenv("KODI_PROFILE_USERDATA", CSpecialProtocol::TranslatePath("special://masterprofile/")); + + CreateUserDirs(); + + return true; +#else + return false; +#endif +} + +void CSettingsComponent::CreateUserDirs() const +{ + XFILE::CDirectory::Create("special://home/"); + XFILE::CDirectory::Create("special://home/addons"); + XFILE::CDirectory::Create("special://home/addons/packages"); + XFILE::CDirectory::Create("special://home/addons/temp"); + XFILE::CDirectory::Create("special://home/media"); + XFILE::CDirectory::Create("special://home/system"); + XFILE::CDirectory::Create("special://masterprofile/"); + XFILE::CDirectory::Create("special://temp/"); + XFILE::CDirectory::Create("special://logpath"); + XFILE::CDirectory::Create("special://temp/temp"); // temp directory for python and dllGetTempPathA + + //Let's clear our archive cache before starting up anything more + auto archiveCachePath = CSpecialProtocol::TranslatePath("special://temp/archive_cache/"); + if (XFILE::CDirectory::Exists(archiveCachePath)) + if (!XFILE::CDirectory::RemoveRecursive(archiveCachePath)) + CLog::Log(LOGWARNING, "Failed to remove the archive cache at {}", archiveCachePath); + XFILE::CDirectory::Create(archiveCachePath); + +} diff --git a/xbmc/settings/SettingsComponent.h b/xbmc/settings/SettingsComponent.h new file mode 100644 index 0000000..42bb9a8 --- /dev/null +++ b/xbmc/settings/SettingsComponent.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +class CAdvancedSettings; +class CProfileManager; +class CSettings; + +namespace KODI +{ +namespace SUBTITLES +{ +class CSubtitlesSettings; +} // namespace SUBTITLES +} // namespace KODI + +class CSettingsComponent +{ +public: + CSettingsComponent(); + virtual ~CSettingsComponent(); + + /*! + * @brief Initialize all subcomponents with system default values (loaded from code, system settings files, ...). + */ + void Initialize(); + + /*! + * @brief Initialize all subcomponents with user values (loaded from user settings files, according to active profile). + * @return true on success, false otherwise. + */ + bool Load(); + + /*! + * @brief Deinitialize all subcomponents. + */ + void Deinitialize(); + + /*! + * @brief Get access to the settings subcomponent. + * @return the settings subcomponent. + */ + std::shared_ptr<CSettings> GetSettings(); + + /*! + * @brief Get access to the advanced settings subcomponent. + * @return the advanced settings subcomponent. + */ + std::shared_ptr<CAdvancedSettings> GetAdvancedSettings(); + + /*! + * @brief Get access to the subtitles settings subcomponent. + * @return the subtiltles settings subcomponent. + */ + std::shared_ptr<KODI::SUBTITLES::CSubtitlesSettings> GetSubtitlesSettings(); + + /*! + * @brief Get access to the profiles manager subcomponent. + * @return the profiles manager subcomponent. + */ + std::shared_ptr<CProfileManager> GetProfileManager(); + +private: + bool InitDirectoriesLinux(bool bPlatformDirectories); + bool InitDirectoriesOSX(bool bPlatformDirectories); + bool InitDirectoriesWin32(bool bPlatformDirectories); + void CreateUserDirs() const; + + enum class State + { + DEINITED, + INITED, + LOADED + }; + State m_state = State::DEINITED; + + std::shared_ptr<CSettings> m_settings; + std::shared_ptr<CAdvancedSettings> m_advancedSettings; + std::shared_ptr<KODI::SUBTITLES::CSubtitlesSettings> m_subtitlesSettings; + std::shared_ptr<CProfileManager> m_profileManager; +}; diff --git a/xbmc/settings/SettingsValueFlatJsonSerializer.cpp b/xbmc/settings/SettingsValueFlatJsonSerializer.cpp new file mode 100644 index 0000000..736c37d --- /dev/null +++ b/xbmc/settings/SettingsValueFlatJsonSerializer.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsValueFlatJsonSerializer.h" + +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingSection.h" +#include "settings/lib/SettingType.h" +#include "settings/lib/SettingsManager.h" +#include "utils/JSONVariantWriter.h" +#include "utils/log.h" + +CSettingsValueFlatJsonSerializer::CSettingsValueFlatJsonSerializer(bool compact /* = true */) + : m_compact(compact) +{ } + +std::string CSettingsValueFlatJsonSerializer::SerializeValues( + const CSettingsManager* settingsManager) const +{ + if (settingsManager == nullptr) + return ""; + + CVariant root(CVariant::VariantTypeObject); + + const auto sections = settingsManager->GetSections(); + for (const auto& section : sections) + SerializeSection(root, section); + + std::string result; + if (!CJSONVariantWriter::Write(root, result, m_compact)) + { + CLog::Log(LOGWARNING, + "CSettingsValueFlatJsonSerializer: failed to serialize settings into JSON"); + return ""; + } + + return result; +} + +void CSettingsValueFlatJsonSerializer::SerializeSection( + CVariant& parent, const std::shared_ptr<CSettingSection>& section) const +{ + if (section == nullptr) + return; + + const auto categories = section->GetCategories(); + for (const auto& category : categories) + SerializeCategory(parent, category); +} + +void CSettingsValueFlatJsonSerializer::SerializeCategory( + CVariant& parent, const std::shared_ptr<CSettingCategory>& category) const +{ + if (category == nullptr) + return; + + const auto groups = category->GetGroups(); + for (const auto& group : groups) + SerializeGroup(parent, group); +} + +void CSettingsValueFlatJsonSerializer::SerializeGroup( + CVariant& parent, const std::shared_ptr<CSettingGroup>& group) const +{ + if (group == nullptr) + return; + + const auto settings = group->GetSettings(); + for (const auto& setting : settings) + SerializeSetting(parent, setting); +} + +void CSettingsValueFlatJsonSerializer::SerializeSetting( + CVariant& parent, const std::shared_ptr<CSetting>& setting) const +{ + if (setting == nullptr) + return; + + // ignore references and action settings (which don't have a value) + if (setting->IsReference() || setting->GetType() == SettingType::Action) + return; + + const auto valueObj = SerializeSettingValue(setting); + if (valueObj.isNull()) + return; + + parent[setting->GetId()] = valueObj; +} + +CVariant CSettingsValueFlatJsonSerializer::SerializeSettingValue( + const std::shared_ptr<CSetting>& setting) const +{ + switch (setting->GetType()) + { + case SettingType::Action: + return CVariant::ConstNullVariant; + + case SettingType::Boolean: + return CVariant(std::static_pointer_cast<CSettingBool>(setting)->GetValue()); + + case SettingType::Integer: + return CVariant(std::static_pointer_cast<CSettingInt>(setting)->GetValue()); + + case SettingType::Number: + return CVariant(std::static_pointer_cast<CSettingNumber>(setting)->GetValue()); + + case SettingType::String: + return CVariant(std::static_pointer_cast<CSettingString>(setting)->GetValue()); + + case SettingType::List: + { + const auto settingList = std::static_pointer_cast<CSettingList>(setting); + + CVariant settingListValuesObj(CVariant::VariantTypeArray); + const auto settingListValues = settingList->GetValue(); + for (const auto& settingListValue : settingListValues) + { + const auto valueObj = SerializeSettingValue(settingListValue); + if (!valueObj.isNull()) + settingListValuesObj.push_back(valueObj); + } + + return settingListValuesObj; + } + + case SettingType::Unknown: + default: + CLog::Log(LOGWARNING, + "CSettingsValueFlatJsonSerializer: failed to serialize setting \"{}\" with value \"{}\" " \ + "of unknown type", setting->GetId(), setting->ToString()); + return CVariant::ConstNullVariant; + } +} diff --git a/xbmc/settings/SettingsValueFlatJsonSerializer.h b/xbmc/settings/SettingsValueFlatJsonSerializer.h new file mode 100644 index 0000000..05214ab --- /dev/null +++ b/xbmc/settings/SettingsValueFlatJsonSerializer.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingsValueSerializer.h" +#include "utils/Variant.h" + +#include <memory> + +class CSetting; +class CSettingCategory; +class CSettingGroup; +class CSettingSection; + +class CSettingsValueFlatJsonSerializer : public ISettingsValueSerializer +{ +public: + explicit CSettingsValueFlatJsonSerializer(bool compact = true); + ~CSettingsValueFlatJsonSerializer() = default; + + void SetCompact(bool compact = true) { m_compact = compact; } + + // implementation of ISettingsValueSerializer + std::string SerializeValues(const CSettingsManager* settingsManager) const override; + +private: + void SerializeSection(CVariant& parent, const std::shared_ptr<CSettingSection>& section) const; + void SerializeCategory(CVariant& parent, const std::shared_ptr<CSettingCategory>& category) const; + void SerializeGroup(CVariant& parent, const std::shared_ptr<CSettingGroup>& group) const; + void SerializeSetting(CVariant& parent, const std::shared_ptr<CSetting>& setting) const; + CVariant SerializeSettingValue(const std::shared_ptr<CSetting>& setting) const; + + bool m_compact; +}; diff --git a/xbmc/settings/SettingsValueXmlSerializer.cpp b/xbmc/settings/SettingsValueXmlSerializer.cpp new file mode 100644 index 0000000..42f83e3 --- /dev/null +++ b/xbmc/settings/SettingsValueXmlSerializer.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsValueXmlSerializer.h" + +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingSection.h" +#include "settings/lib/SettingsManager.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +static constexpr char SETTINGS_XML_ROOT[] = "settings"; + +std::string CSettingsValueXmlSerializer::SerializeValues( + const CSettingsManager* settingsManager) const +{ + if (settingsManager == nullptr) + return ""; + + CXBMCTinyXML xmlDoc; + TiXmlElement rootElement(SETTINGS_XML_ROOT); + rootElement.SetAttribute(SETTING_XML_ROOT_VERSION, settingsManager->GetVersion()); + TiXmlNode* xmlRoot = xmlDoc.InsertEndChild(rootElement); + if (xmlRoot == nullptr) + return ""; + + const auto sections = settingsManager->GetSections(); + for (const auto& section : sections) + SerializeSection(xmlRoot, section); + + std::stringstream stream; + stream << *xmlDoc.RootElement(); + + return stream.str(); +} + +void CSettingsValueXmlSerializer::SerializeSection( + TiXmlNode* parent, const std::shared_ptr<CSettingSection>& section) const +{ + if (section == nullptr) + return; + + const auto categories = section->GetCategories(); + for (const auto& category : categories) + SerializeCategory(parent, category); +} + +void CSettingsValueXmlSerializer::SerializeCategory( + TiXmlNode* parent, const std::shared_ptr<CSettingCategory>& category) const +{ + if (category == nullptr) + return; + + const auto groups = category->GetGroups(); + for (const auto& group : groups) + SerializeGroup(parent, group); +} + +void CSettingsValueXmlSerializer::SerializeGroup(TiXmlNode* parent, + const std::shared_ptr<CSettingGroup>& group) const +{ + if (group == nullptr) + return; + + const auto settings = group->GetSettings(); + for (const auto& setting : settings) + SerializeSetting(parent, setting); +} + +void CSettingsValueXmlSerializer::SerializeSetting(TiXmlNode* parent, + const std::shared_ptr<CSetting>& setting) const +{ + if (setting == nullptr) + return; + + // ignore references and action settings (which don't have a value) + if (setting->IsReference() || setting->GetType() == SettingType::Action) + return; + + TiXmlElement settingElement(SETTING_XML_ELM_SETTING); + settingElement.SetAttribute(SETTING_XML_ATTR_ID, setting->GetId()); + + // add the default attribute + if (setting->IsDefault()) + settingElement.SetAttribute(SETTING_XML_ELM_DEFAULT, "true"); + + // add the value + TiXmlText value(setting->ToString()); + settingElement.InsertEndChild(value); + + if (parent->InsertEndChild(settingElement) == nullptr) + CLog::Log(LOGWARNING, + "CSettingsValueXmlSerializer: unable to write <" SETTING_XML_ELM_SETTING " id=\"{}\"> tag", + setting->GetId()); +} diff --git a/xbmc/settings/SettingsValueXmlSerializer.h b/xbmc/settings/SettingsValueXmlSerializer.h new file mode 100644 index 0000000..010c036 --- /dev/null +++ b/xbmc/settings/SettingsValueXmlSerializer.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingsValueSerializer.h" + +#include <memory> + +class CSetting; +class CSettingCategory; +class CSettingGroup; +class CSettingSection; +class TiXmlNode; + +class CSettingsValueXmlSerializer : public ISettingsValueSerializer +{ +public: + CSettingsValueXmlSerializer() = default; + ~CSettingsValueXmlSerializer() = default; + + // implementation of ISettingsValueSerializer + std::string SerializeValues(const CSettingsManager* settingsManager) const override; + +private: + void SerializeSection(TiXmlNode* parent, const std::shared_ptr<CSettingSection>& section) const; + void SerializeCategory(TiXmlNode* parent, + const std::shared_ptr<CSettingCategory>& category) const; + void SerializeGroup(TiXmlNode* parent, const std::shared_ptr<CSettingGroup>& group) const; + void SerializeSetting(TiXmlNode* parent, const std::shared_ptr<CSetting>& setting) const; +}; diff --git a/xbmc/settings/SkinSettings.cpp b/xbmc/settings/SkinSettings.cpp new file mode 100644 index 0000000..71ff616 --- /dev/null +++ b/xbmc/settings/SkinSettings.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SkinSettings.h" + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "addons/Skin.h" +#include "guilib/GUIComponent.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <memory> +#include <mutex> +#include <string> + +#define XML_SKINSETTINGS "skinsettings" + +CSkinSettings::CSkinSettings() +{ + Clear(); +} + +CSkinSettings::~CSkinSettings() = default; + +CSkinSettings& CSkinSettings::GetInstance() +{ + static CSkinSettings sSkinSettings; + return sSkinSettings; +} + +int CSkinSettings::TranslateString(const std::string &setting) +{ + return g_SkinInfo->TranslateString(setting); +} + +const std::string& CSkinSettings::GetString(int setting) const +{ + return g_SkinInfo->GetString(setting); +} + +void CSkinSettings::SetString(int setting, const std::string &label) +{ + g_SkinInfo->SetString(setting, label); +} + +int CSkinSettings::TranslateBool(const std::string &setting) +{ + return g_SkinInfo->TranslateBool(setting); +} + +bool CSkinSettings::GetBool(int setting) const +{ + return g_SkinInfo->GetBool(setting); +} + +int CSkinSettings::GetInt(int setting) const +{ + return g_SkinInfo->GetInt(setting); +} + +void CSkinSettings::SetBool(int setting, bool set) +{ + g_SkinInfo->SetBool(setting, set); +} + +void CSkinSettings::Reset(const std::string &setting) +{ + g_SkinInfo->Reset(setting); +} + +std::set<ADDON::CSkinSettingPtr> CSkinSettings::GetSettings() const +{ + return g_SkinInfo->GetSkinSettings(); +} + +ADDON::CSkinSettingPtr CSkinSettings::GetSetting(const std::string& settingId) +{ + return g_SkinInfo->GetSkinSetting(settingId); +} + +std::shared_ptr<const ADDON::CSkinSetting> CSkinSettings::GetSetting( + const std::string& settingId) const +{ + return g_SkinInfo->GetSkinSetting(settingId); +} + +void CSkinSettings::Reset() +{ + g_SkinInfo->Reset(); + + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + infoMgr.ResetCache(); + infoMgr.GetInfoProviders().GetGUIControlsInfoProvider().ResetContainerMovingCache(); +} + +bool CSkinSettings::Load(const TiXmlNode *settings) +{ + if (settings == nullptr) + return false; + + const TiXmlElement *rootElement = settings->FirstChildElement(XML_SKINSETTINGS); + + // return true in the case skinsettings is missing. It just means that + // it's been migrated and it's not an error + if (rootElement == nullptr) + { + CLog::Log(LOGDEBUG, "CSkinSettings: no <skinsettings> tag found"); + return true; + } + + std::unique_lock<CCriticalSection> lock(m_critical); + m_settings.clear(); + m_settings = ADDON::CSkinInfo::ParseSettings(rootElement); + + return true; +} + +bool CSkinSettings::Save(TiXmlNode *settings) const +{ + if (settings == nullptr) + return false; + + // nothing to do here because skin settings saving has been migrated to CSkinInfo + + return true; +} + +void CSkinSettings::Clear() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + m_settings.clear(); +} + +void CSkinSettings::MigrateSettings(const std::shared_ptr<ADDON::CSkinInfo>& skin) +{ + if (skin == nullptr) + return; + + std::unique_lock<CCriticalSection> lock(m_critical); + + bool settingsMigrated = false; + const std::string& skinId = skin->ID(); + std::set<ADDON::CSkinSettingPtr> settingsCopy(m_settings.begin(), m_settings.end()); + for (const auto& setting : settingsCopy) + { + if (!StringUtils::StartsWith(setting->name, skinId + ".")) + continue; + + std::string settingName = setting->name.substr(skinId.size() + 1); + + if (setting->GetType() == "string") + { + int settingNumber = skin->TranslateString(settingName); + if (settingNumber >= 0) + skin->SetString(settingNumber, std::dynamic_pointer_cast<ADDON::CSkinSettingString>(setting)->value); + } + else if (setting->GetType() == "bool") + { + int settingNumber = skin->TranslateBool(settingName); + if (settingNumber >= 0) + skin->SetBool(settingNumber, std::dynamic_pointer_cast<ADDON::CSkinSettingBool>(setting)->value); + } + + m_settings.erase(setting); + settingsMigrated = true; + } + + if (settingsMigrated) + { + // save the skin's settings + skin->SaveSettings(); + + // save the guisettings.xml + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + } +} + diff --git a/xbmc/settings/SkinSettings.h b/xbmc/settings/SkinSettings.h new file mode 100644 index 0000000..9ade245 --- /dev/null +++ b/xbmc/settings/SkinSettings.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "addons/Skin.h" +#include "settings/ISubSettings.h" +#include "threads/CriticalSection.h" + +#include <memory> +#include <set> +#include <string> + +class TiXmlNode; + +class CSkinSettings : public ISubSettings +{ +public: + static CSkinSettings& GetInstance(); + + bool Load(const TiXmlNode *settings) override; + bool Save(TiXmlNode *settings) const override; + void Clear() override; + + void MigrateSettings(const std::shared_ptr<ADDON::CSkinInfo>& skin); + + int TranslateString(const std::string &setting); + const std::string& GetString(int setting) const; + void SetString(int setting, const std::string &label); + + int TranslateBool(const std::string &setting); + bool GetBool(int setting) const; + void SetBool(int setting, bool set); + + /*! \brief Get the skin setting value as an integer value + * \param setting - the setting id + * \return the setting value as an integer, -1 if no conversion is possible + */ + int GetInt(int setting) const; + + std::set<ADDON::CSkinSettingPtr> GetSettings() const; + ADDON::CSkinSettingPtr GetSetting(const std::string& settingId); + std::shared_ptr<const ADDON::CSkinSetting> GetSetting(const std::string& settingId) const; + + void Reset(const std::string &setting); + void Reset(); + +protected: + CSkinSettings(); + CSkinSettings(const CSkinSettings&) = delete; + CSkinSettings& operator=(CSkinSettings const&) = delete; + ~CSkinSettings() override; + +private: + CCriticalSection m_critical; + std::set<ADDON::CSkinSettingPtr> m_settings; +}; diff --git a/xbmc/settings/SubtitlesSettings.cpp b/xbmc/settings/SubtitlesSettings.cpp new file mode 100644 index 0000000..66ef636 --- /dev/null +++ b/xbmc/settings/SubtitlesSettings.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2012-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SubtitlesSettings.h" + +#include "guilib/GUIFontManager.h" +#include "guilib/LocalizeStrings.h" +#include "settings/Settings.h" +#include "settings/lib/Setting.h" +#include "utils/FileUtils.h" +#include "utils/FontUtils.h" +#include "utils/URIUtils.h" + +using namespace KODI; +using namespace SUBTITLES; + +CSubtitlesSettings::CSubtitlesSettings(const std::shared_ptr<CSettings>& settings) + : m_settings(settings) +{ + m_settings->RegisterCallback( + this, + {CSettings::SETTING_LOCALE_SUBTITLELANGUAGE, CSettings::SETTING_SUBTITLES_PARSECAPTIONS, + CSettings::SETTING_SUBTITLES_ALIGN, CSettings::SETTING_SUBTITLES_STEREOSCOPICDEPTH, + CSettings::SETTING_SUBTITLES_FONTNAME, CSettings::SETTING_SUBTITLES_FONTSIZE, + CSettings::SETTING_SUBTITLES_STYLE, CSettings::SETTING_SUBTITLES_COLOR, + CSettings::SETTING_SUBTITLES_BORDERSIZE, CSettings::SETTING_SUBTITLES_BORDERCOLOR, + CSettings::SETTING_SUBTITLES_OPACITY, CSettings::SETTING_SUBTITLES_BGCOLOR, + CSettings::SETTING_SUBTITLES_BGOPACITY, CSettings::SETTING_SUBTITLES_BLUR, + CSettings::SETTING_SUBTITLES_BACKGROUNDTYPE, CSettings::SETTING_SUBTITLES_SHADOWCOLOR, + CSettings::SETTING_SUBTITLES_SHADOWOPACITY, CSettings::SETTING_SUBTITLES_SHADOWSIZE, + CSettings::SETTING_SUBTITLES_MARGINVERTICAL, CSettings::SETTING_SUBTITLES_CHARSET, + CSettings::SETTING_SUBTITLES_OVERRIDEFONTS, CSettings::SETTING_SUBTITLES_OVERRIDESTYLES, + CSettings::SETTING_SUBTITLES_LANGUAGES, CSettings::SETTING_SUBTITLES_STORAGEMODE, + CSettings::SETTING_SUBTITLES_CUSTOMPATH, CSettings::SETTING_SUBTITLES_PAUSEONSEARCH, + CSettings::SETTING_SUBTITLES_DOWNLOADFIRST, CSettings::SETTING_SUBTITLES_TV, + CSettings::SETTING_SUBTITLES_MOVIE}); +} + +CSubtitlesSettings::~CSubtitlesSettings() +{ + m_settings->UnregisterCallback(this); +} + +void CSubtitlesSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return; + + SetChanged(); + NotifyObservers(ObservableMessageSettingsChanged); + if (setting->GetId() == CSettings::SETTING_SUBTITLES_ALIGN) + { + SetChanged(); + NotifyObservers(ObservableMessagePositionChanged); + } +} + +Align CSubtitlesSettings::GetAlignment() +{ + return static_cast<Align>(m_settings->GetInt(CSettings::SETTING_SUBTITLES_ALIGN)); +} + +void CSubtitlesSettings::SetAlignment(Align align) +{ + m_settings->SetInt(CSettings::SETTING_SUBTITLES_ALIGN, static_cast<int>(align)); +} + +HorizontalAlign CSubtitlesSettings::GetHorizontalAlignment() +{ + return static_cast<HorizontalAlign>( + m_settings->GetInt(CSettings::SETTING_SUBTITLES_CAPTIONSALIGN)); +} + +std::string CSubtitlesSettings::GetFontName() +{ + return m_settings->GetString(CSettings::SETTING_SUBTITLES_FONTNAME); +} + +FontStyle CSubtitlesSettings::GetFontStyle() +{ + return static_cast<FontStyle>(m_settings->GetInt(CSettings::SETTING_SUBTITLES_STYLE)); +} + +int CSubtitlesSettings::GetFontSize() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_FONTSIZE); +} + +UTILS::COLOR::Color CSubtitlesSettings::GetFontColor() +{ + return UTILS::COLOR::ConvertHexToColor(m_settings->GetString(CSettings::SETTING_SUBTITLES_COLOR)); +} + +int CSubtitlesSettings::GetFontOpacity() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_OPACITY); +} + +int CSubtitlesSettings::GetBorderSize() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_BORDERSIZE); +} + +UTILS::COLOR::Color CSubtitlesSettings::GetBorderColor() +{ + return UTILS::COLOR::ConvertHexToColor( + m_settings->GetString(CSettings::SETTING_SUBTITLES_BORDERCOLOR)); +} + +int CSubtitlesSettings::GetShadowSize() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_SHADOWSIZE); +} + +UTILS::COLOR::Color CSubtitlesSettings::GetShadowColor() +{ + return UTILS::COLOR::ConvertHexToColor( + m_settings->GetString(CSettings::SETTING_SUBTITLES_SHADOWCOLOR)); +} + +int CSubtitlesSettings::GetShadowOpacity() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_SHADOWOPACITY); +} + +int CSubtitlesSettings::GetBlurSize() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_BLUR); +} + +BackgroundType CSubtitlesSettings::GetBackgroundType() +{ + return static_cast<BackgroundType>( + m_settings->GetInt(CSettings::SETTING_SUBTITLES_BACKGROUNDTYPE)); +} + +UTILS::COLOR::Color CSubtitlesSettings::GetBackgroundColor() +{ + return UTILS::COLOR::ConvertHexToColor( + m_settings->GetString(CSettings::SETTING_SUBTITLES_BGCOLOR)); +} + +int CSubtitlesSettings::GetBackgroundOpacity() +{ + return m_settings->GetInt(CSettings::SETTING_SUBTITLES_BGOPACITY); +} + +bool CSubtitlesSettings::IsOverrideFonts() +{ + return m_settings->GetBool(CSettings::SETTING_SUBTITLES_OVERRIDEFONTS); +} + +OverrideStyles CSubtitlesSettings::GetOverrideStyles() +{ + return static_cast<OverrideStyles>( + m_settings->GetInt(CSettings::SETTING_SUBTITLES_OVERRIDESTYLES)); +} + +float CSubtitlesSettings::GetVerticalMarginPerc() +{ + // We return the vertical margin as percentage + // to fit the current screen resolution + return static_cast<float>(m_settings->GetNumber(CSettings::SETTING_SUBTITLES_MARGINVERTICAL)); +} + +void CSubtitlesSettings::SettingOptionsSubtitleFontsFiller(const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + // From application system fonts folder we add the default font only + std::string defaultFontPath = + URIUtils::AddFileToFolder("special://xbmc/media/Fonts/", UTILS::FONT::FONT_DEFAULT_FILENAME); + if (CFileUtils::Exists(defaultFontPath)) + { + std::string familyName = UTILS::FONT::GetFontFamily(defaultFontPath); + if (!familyName.empty()) + { + list.emplace_back(g_localizeStrings.Get(571) + " " + familyName, FONT_DEFAULT_FAMILYNAME); + } + } + // Add additionals fonts from the user fonts folder + for (const std::string& familyName : g_fontManager.GetUserFontsFamilyNames()) + { + list.emplace_back(familyName, familyName); + } +} diff --git a/xbmc/settings/SubtitlesSettings.h b/xbmc/settings/SubtitlesSettings.h new file mode 100644 index 0000000..3927de1 --- /dev/null +++ b/xbmc/settings/SubtitlesSettings.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/lib/ISettingCallback.h" +#include "utils/ColorUtils.h" +#include "utils/Observer.h" + +#include <memory> +#include <string> + +class CSetting; +class CSettings; +struct StringSettingOption; + +namespace KODI +{ +namespace SUBTITLES +{ +// This is a placeholder to keep the fontname setting valid +// even if the default app font could be changed +constexpr const char* FONT_DEFAULT_FAMILYNAME = "DEFAULT"; + +enum class Align +{ + MANUAL = 0, + BOTTOM_INSIDE, + BOTTOM_OUTSIDE, + TOP_INSIDE, + TOP_OUTSIDE +}; + +enum class HorizontalAlign +{ + LEFT = 0, + CENTER, + RIGHT +}; + +enum class BackgroundType +{ + NONE = 0, + SHADOW, + BOX, + SQUAREBOX +}; + +enum class FontStyle +{ + NORMAL = 0, + BOLD, + ITALIC, + BOLD_ITALIC +}; + +enum class OverrideStyles +{ + DISABLED = 0, + POSITIONS, + STYLES, + STYLES_POSITIONS +}; + +class CSubtitlesSettings : public ISettingCallback, public Observable +{ +public: + explicit CSubtitlesSettings(const std::shared_ptr<CSettings>& settings); + ~CSubtitlesSettings() override; + + // Inherited from ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + /*! + * \brief Get subtitle alignment + * \return The alignment + */ + Align GetAlignment(); + + /*! + * \brief Set the subtitle alignment + * \param align The alignment + */ + void SetAlignment(Align align); + + /*! + * \brief Get horizontal text alignment + * \return The alignment + */ + HorizontalAlign GetHorizontalAlignment(); + + /*! + * \brief Get font name + * \return The font name + */ + std::string GetFontName(); + + /*! + * \brief Get font style + * \return The font style + */ + FontStyle GetFontStyle(); + + /*! + * \brief Get font size + * \return The font size in PX + */ + int GetFontSize(); + + /*! + * \brief Get font color + * \return The font color + */ + UTILS::COLOR::Color GetFontColor(); + + /*! + * \brief Get font opacity + * \return The font opacity in % + */ + int GetFontOpacity(); + + /*! + * \brief Get border size + * \return The border size in % + */ + int GetBorderSize(); + + /*! + * \brief Get border color + * \return The border color + */ + UTILS::COLOR::Color GetBorderColor(); + + /*! + * \brief Get shadow size + * \return The shadow size in % + */ + int GetShadowSize(); + + /*! + * \brief Get shadow color + * \return The shadow color + */ + UTILS::COLOR::Color GetShadowColor(); + + /*! + * \brief Get shadow opacity + * \return The shadow opacity in % + */ + int GetShadowOpacity(); + + /*! + * \brief Get blur size + * \return The blur size in % + */ + int GetBlurSize(); + + /*! + * \brief Get background type + * \return The background type + */ + BackgroundType GetBackgroundType(); + + /*! + * \brief Get background color + * \return The background color + */ + UTILS::COLOR::Color GetBackgroundColor(); + + /*! + * \brief Get background opacity + * \return The background opacity in % + */ + int GetBackgroundOpacity(); + + /*! + * \brief Check if font override is enabled + * \return True if fonts must be overriden, otherwise false + */ + bool IsOverrideFonts(); + + /*! + * \brief Get override styles + * \return The styles to be overriden + */ + OverrideStyles GetOverrideStyles(); + + /*! + * \brief Get the subtitle vertical margin + * \return The vertical margin in % + */ + float GetVerticalMarginPerc(); + + static void SettingOptionsSubtitleFontsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); + +private: + CSubtitlesSettings() = delete; + + const std::shared_ptr<CSettings> m_settings; +}; + +} // namespace SUBTITLES +} // namespace KODI diff --git a/xbmc/settings/dialogs/CMakeLists.txt b/xbmc/settings/dialogs/CMakeLists.txt new file mode 100644 index 0000000..631e86c --- /dev/null +++ b/xbmc/settings/dialogs/CMakeLists.txt @@ -0,0 +1,13 @@ +set(SOURCES GUIDialogContentSettings.cpp + GUIDialogLibExportSettings.cpp + GUIDialogSettingsBase.cpp + GUIDialogSettingsManagerBase.cpp + GUIDialogSettingsManualBase.cpp) + +set(HEADERS GUIDialogContentSettings.h + GUIDialogLibExportSettings.h + GUIDialogSettingsBase.h + GUIDialogSettingsManagerBase.h + GUIDialogSettingsManualBase.h) + +core_add_library(settings_dialogs) diff --git a/xbmc/settings/dialogs/GUIDialogContentSettings.cpp b/xbmc/settings/dialogs/GUIDialogContentSettings.cpp new file mode 100644 index 0000000..da4caf1 --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogContentSettings.cpp @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogContentSettings.h" + +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/AddonSystemSettings.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "addons/gui/GUIWindowAddonBrowser.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogSelect.h" +#include "filesystem/AddonsDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingsManager.h" +#include "settings/windows/GUIControlSettings.h" +#include "utils/log.h" +#include "video/VideoInfoScanner.h" + +#include <limits.h> +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#define SETTING_CONTENT_TYPE "contenttype" +#define SETTING_SCRAPER_LIST "scraperlist" +#define SETTING_SCRAPER_SETTINGS "scrapersettings" +#define SETTING_SCAN_RECURSIVE "scanrecursive" +#define SETTING_USE_DIRECTORY_NAMES "usedirectorynames" +#define SETTING_CONTAINS_SINGLE_ITEM "containssingleitem" +#define SETTING_EXCLUDE "exclude" +#define SETTING_NO_UPDATING "noupdating" +#define SETTING_ALL_EXTERNAL_AUDIO "allexternalaudio" + +using namespace ADDON; + + +CGUIDialogContentSettings::CGUIDialogContentSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_CONTENT_SETTINGS, "DialogSettings.xml") +{ } + +void CGUIDialogContentSettings::SetContent(CONTENT_TYPE content) +{ + m_content = m_originalContent = content; +} + +void CGUIDialogContentSettings::ResetContent() +{ + SetContent(CONTENT_NONE); +} + +void CGUIDialogContentSettings::SetScanSettings(const VIDEO::SScanSettings &scanSettings) +{ + m_scanRecursive = (scanSettings.recurse > 0 && !scanSettings.parent_name) || + (scanSettings.recurse > 1 && scanSettings.parent_name); + m_useDirectoryNames = scanSettings.parent_name; + m_exclude = scanSettings.exclude; + m_containsSingleItem = scanSettings.parent_name_root; + m_noUpdating = scanSettings.noupdate; + m_allExternalAudio = scanSettings.m_allExtAudio; +} + +bool CGUIDialogContentSettings::Show(ADDON::ScraperPtr& scraper, CONTENT_TYPE content /* = CONTENT_NONE */) +{ + VIDEO::SScanSettings dummy; + return Show(scraper, dummy, content); +} + +bool CGUIDialogContentSettings::Show(ADDON::ScraperPtr& scraper, VIDEO::SScanSettings& settings, CONTENT_TYPE content /* = CONTENT_NONE */) +{ + CGUIDialogContentSettings *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogContentSettings>(WINDOW_DIALOG_CONTENT_SETTINGS); + if (dialog == NULL) + return false; + + if (scraper != NULL) + { + dialog->SetContent(content != CONTENT_NONE ? content : scraper->Content()); + dialog->SetScraper(scraper); + // toast selected but disabled scrapers + if (CServiceBroker::GetAddonMgr().IsAddonDisabled(scraper->ID())) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(24024), scraper->Name(), 2000, true); + } + + dialog->SetScanSettings(settings); + dialog->Open(); + + bool confirmed = dialog->IsConfirmed(); + if (confirmed) + { + scraper = dialog->GetScraper(); + content = dialog->GetContent(); + + settings.m_allExtAudio = dialog->GetUseAllExternalAudio(); + + if (scraper == NULL || content == CONTENT_NONE) + settings.exclude = dialog->GetExclude(); + else + { + settings.exclude = false; + settings.noupdate = dialog->GetNoUpdating(); + scraper->SetPathSettings(content, ""); + + if (content == CONTENT_TVSHOWS) + { + settings.parent_name = settings.parent_name_root = dialog->GetContainsSingleItem(); + settings.recurse = 0; + } + else if (content == CONTENT_MOVIES || content == CONTENT_MUSICVIDEOS) + { + if (dialog->GetUseDirectoryNames()) + { + settings.parent_name = true; + settings.parent_name_root = false; + settings.recurse = dialog->GetScanRecursive() ? INT_MAX : 1; + + if (dialog->GetContainsSingleItem()) + { + settings.parent_name_root = true; + settings.recurse = 0; + } + } + else + { + settings.parent_name = false; + settings.parent_name_root = false; + settings.recurse = dialog->GetScanRecursive() ? INT_MAX : 0; + } + } + } + } + + // now that we have evaluated all settings we need to reset the content + dialog->ResetContent(); + + return confirmed; +} + +void CGUIDialogContentSettings::OnInitWindow() +{ + CGUIDialogSettingsManualBase::OnInitWindow(); +} + +void CGUIDialogContentSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_CONTAINS_SINGLE_ITEM) + m_containsSingleItem = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == SETTING_NO_UPDATING) + m_noUpdating = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == SETTING_USE_DIRECTORY_NAMES) + m_useDirectoryNames = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == SETTING_SCAN_RECURSIVE) + { + m_scanRecursive = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + GetSettingsManager()->SetBool(SETTING_CONTAINS_SINGLE_ITEM, false); + } + else if (settingId == SETTING_EXCLUDE) + m_exclude = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == SETTING_ALL_EXTERNAL_AUDIO) + m_allExternalAudio = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); +} + +void CGUIDialogContentSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingAction(setting); + + const std::string &settingId = setting->GetId(); + + if (settingId == SETTING_CONTENT_TYPE) + { + std::vector<std::pair<std::string, int>> labels; + if (m_content == CONTENT_ALBUMS || m_content == CONTENT_ARTISTS) + { + labels.emplace_back(ADDON::TranslateContent(m_content, true), m_content); + } + else + { + labels.emplace_back(ADDON::TranslateContent(CONTENT_NONE, true), CONTENT_NONE); + labels.emplace_back(ADDON::TranslateContent(CONTENT_MOVIES, true), CONTENT_MOVIES); + labels.emplace_back(ADDON::TranslateContent(CONTENT_TVSHOWS, true), CONTENT_TVSHOWS); + labels.emplace_back(ADDON::TranslateContent(CONTENT_MUSICVIDEOS, true), CONTENT_MUSICVIDEOS); + } + std::sort(labels.begin(), labels.end()); + + CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + if (dialog) + { + dialog->SetHeading(CVariant{ 20344 }); //Label "This directory contains" + + int iIndex = 0; + int iSelected = 0; + for (const auto &label : labels) + { + dialog->Add(label.first); + + if (m_content == label.second) + iSelected = iIndex; + iIndex++; + } + + dialog->SetSelected(iSelected); + + dialog->Open(); + // Selected item has not changes - in case of cancel or the user selecting the same item + int newSelected = dialog->GetSelectedItem(); + if (!dialog->IsConfirmed() || newSelected < 0 || newSelected == iSelected) + return; + + auto selected = labels.at(newSelected); + m_content = static_cast<CONTENT_TYPE>(selected.second); + + AddonPtr scraperAddon; + if (!CAddonSystemSettings::GetInstance().GetActive(ADDON::ScraperTypeFromContent(m_content), scraperAddon) && m_content != CONTENT_NONE) + return; + + m_scraper = std::dynamic_pointer_cast<CScraper>(scraperAddon); + + SetupView(); + SetFocusToSetting(SETTING_CONTENT_TYPE); + } + } + else if (settingId == SETTING_SCRAPER_LIST) + { + ADDON::AddonType type = ADDON::ScraperTypeFromContent(m_content); + std::string currentScraperId; + if (m_scraper != nullptr) + currentScraperId = m_scraper->ID(); + std::string selectedAddonId = currentScraperId; + + if (CGUIWindowAddonBrowser::SelectAddonID(type, selectedAddonId, false) == 1 + && selectedAddonId != currentScraperId) + { + AddonPtr scraperAddon; + if (CServiceBroker::GetAddonMgr().GetAddon(selectedAddonId, scraperAddon, + ADDON::OnlyEnabled::CHOICE_YES)) + { + m_scraper = std::dynamic_pointer_cast<CScraper>(scraperAddon); + SetupView(); + SetFocusToSetting(SETTING_SCRAPER_LIST); + } + else + { + CLog::Log(LOGERROR, "{} - Could not get settings for addon: {}", __FUNCTION__, + selectedAddonId); + } + } + } + else if (settingId == SETTING_SCRAPER_SETTINGS) + CGUIDialogAddonSettings::ShowForAddon(m_scraper, false); +} + +bool CGUIDialogContentSettings::Save() +{ + //Should be saved by caller of ::Show + return true; +} + +void CGUIDialogContentSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + SetHeading(20333); + + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 186); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222); + + SetLabel2(SETTING_CONTENT_TYPE, ADDON::TranslateContent(m_content, true)); + + if (m_content == CONTENT_NONE) + { + ToggleState(SETTING_SCRAPER_LIST, false); + ToggleState(SETTING_SCRAPER_SETTINGS, false); + } + else + { + ToggleState(SETTING_SCRAPER_LIST, true); + if (m_scraper != NULL && !CServiceBroker::GetAddonMgr().IsAddonDisabled(m_scraper->ID())) + { + SetLabel2(SETTING_SCRAPER_LIST, m_scraper->Name()); + if (m_scraper && m_scraper->Supports(m_content) && m_scraper->HasSettings()) + ToggleState(SETTING_SCRAPER_SETTINGS, true); + else + ToggleState(SETTING_SCRAPER_SETTINGS, false); + } + else + { + SetLabel2(SETTING_SCRAPER_LIST, g_localizeStrings.Get(231)); //Set label2 to "None" + ToggleState(SETTING_SCRAPER_SETTINGS, false); + } + } +} + +void CGUIDialogContentSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + if (m_content == CONTENT_NONE) + m_showScanSettings = false; + else if (m_scraper != NULL && !CServiceBroker::GetAddonMgr().IsAddonDisabled(m_scraper->ID())) + m_showScanSettings = true; + + std::shared_ptr<CSettingCategory> category = AddCategory("contentsettings", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogContentSettings: unable to setup settings"); + return; + } + + std::shared_ptr<CSettingGroup> group = AddGroup(category); + if (group == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogContentSettings: unable to setup settings"); + return; + } + + AddButton(group, SETTING_CONTENT_TYPE, 20344, SettingLevel::Basic); + AddButton(group, SETTING_SCRAPER_LIST, 38025, SettingLevel::Basic); + std::shared_ptr<CSettingAction> subsetting = AddButton(group, SETTING_SCRAPER_SETTINGS, 10004, SettingLevel::Basic); + if (subsetting != NULL) + subsetting->SetParent(SETTING_SCRAPER_LIST); + + std::shared_ptr<CSettingGroup> groupDetails = AddGroup(category, 20322); + if (groupDetails == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogContentSettings: unable to setup scanning settings"); + return; + } + switch (m_content) + { + case CONTENT_TVSHOWS: + { + AddToggle(groupDetails, SETTING_CONTAINS_SINGLE_ITEM, 20379, SettingLevel::Basic, m_containsSingleItem, false, m_showScanSettings); + AddToggle(groupDetails, SETTING_NO_UPDATING, 20432, SettingLevel::Basic, m_noUpdating, false, m_showScanSettings); + AddToggle(groupDetails, SETTING_ALL_EXTERNAL_AUDIO, 39120, SettingLevel::Basic, + m_allExternalAudio, false, m_showScanSettings); + break; + } + + case CONTENT_MOVIES: + case CONTENT_MUSICVIDEOS: + { + AddToggle(groupDetails, SETTING_USE_DIRECTORY_NAMES, m_content == CONTENT_MOVIES ? 20329 : 20330, SettingLevel::Basic, m_useDirectoryNames, false, m_showScanSettings); + std::shared_ptr<CSettingBool> settingScanRecursive = AddToggle(groupDetails, SETTING_SCAN_RECURSIVE, 20346, SettingLevel::Basic, m_scanRecursive, false, m_showScanSettings); + std::shared_ptr<CSettingBool> settingContainsSingleItem = AddToggle(groupDetails, SETTING_CONTAINS_SINGLE_ITEM, 20383, SettingLevel::Basic, m_containsSingleItem, false, m_showScanSettings); + AddToggle(groupDetails, SETTING_NO_UPDATING, 20432, SettingLevel::Basic, m_noUpdating, false, m_showScanSettings); + AddToggle(groupDetails, SETTING_ALL_EXTERNAL_AUDIO, 39120, SettingLevel::Basic, + m_allExternalAudio, false, m_showScanSettings); + + // define an enable dependency with (m_useDirectoryNames && !m_containsSingleItem) || !m_useDirectoryNames + CSettingDependency dependencyScanRecursive(SettingDependencyType::Enable, GetSettingsManager()); + dependencyScanRecursive.Or() + ->Add(CSettingDependencyConditionCombinationPtr((new CSettingDependencyConditionCombination(BooleanLogicOperationAnd, GetSettingsManager())) // m_useDirectoryNames && !m_containsSingleItem + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_USE_DIRECTORY_NAMES, "true", SettingDependencyOperator::Equals, false, GetSettingsManager()))) // m_useDirectoryNames + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_CONTAINS_SINGLE_ITEM, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))))) // !m_containsSingleItem + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_USE_DIRECTORY_NAMES, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))); // !m_useDirectoryNames + + // define an enable dependency with m_useDirectoryNames && !m_scanRecursive + CSettingDependency dependencyContainsSingleItem(SettingDependencyType::Enable, GetSettingsManager()); + dependencyContainsSingleItem.And() + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_USE_DIRECTORY_NAMES, "true", SettingDependencyOperator::Equals, false, GetSettingsManager()))) // m_useDirectoryNames + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_SCAN_RECURSIVE, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))); // !m_scanRecursive + + SettingDependencies deps; + deps.push_back(dependencyScanRecursive); + settingScanRecursive->SetDependencies(deps); + + deps.clear(); + deps.push_back(dependencyContainsSingleItem); + settingContainsSingleItem->SetDependencies(deps); + break; + } + + case CONTENT_ALBUMS: + case CONTENT_ARTISTS: + break; + + case CONTENT_NONE: + default: + AddToggle(groupDetails, SETTING_EXCLUDE, 20380, SettingLevel::Basic, m_exclude, false, !m_showScanSettings); + AddToggle(groupDetails, SETTING_ALL_EXTERNAL_AUDIO, 39120, SettingLevel::Basic, + m_allExternalAudio, false, !m_showScanSettings); + break; + } +} + +void CGUIDialogContentSettings::SetLabel2(const std::string &settingid, const std::string &label) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + SET_CONTROL_LABEL2(settingControl->GetID(), label); +} + +void CGUIDialogContentSettings::ToggleState(const std::string &settingid, bool enabled) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + { + if (enabled) + CONTROL_ENABLE(settingControl->GetID()); + else + CONTROL_DISABLE(settingControl->GetID()); + } +} + +void CGUIDialogContentSettings::SetFocusToSetting(const std::string& settingid) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + SET_CONTROL_FOCUS(settingControl->GetID(), 0); +} diff --git a/xbmc/settings/dialogs/GUIDialogContentSettings.h b/xbmc/settings/dialogs/GUIDialogContentSettings.h new file mode 100644 index 0000000..c0d7c81 --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogContentSettings.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "addons/Scraper.h" +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +#include <map> +#include <utility> + +namespace VIDEO +{ + struct SScanSettings; +} +class CFileItemList; + +class CGUIDialogContentSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogContentSettings(); + + // specialization of CGUIWindow + bool HasListItems() const override { return true; } + + CONTENT_TYPE GetContent() const { return m_content; } + void SetContent(CONTENT_TYPE content); + void ResetContent(); + + const ADDON::ScraperPtr& GetScraper() const { return m_scraper; } + void SetScraper(ADDON::ScraperPtr scraper) { m_scraper = std::move(scraper); } + + void SetScanSettings(const VIDEO::SScanSettings &scanSettings); + bool GetScanRecursive() const { return m_scanRecursive; } + bool GetUseDirectoryNames() const { return m_useDirectoryNames; } + bool GetContainsSingleItem() const { return m_containsSingleItem; } + bool GetExclude() const { return m_exclude; } + bool GetNoUpdating() const { return m_noUpdating; } + bool GetUseAllExternalAudio() const { return m_allExternalAudio; } + + static bool Show(ADDON::ScraperPtr& scraper, CONTENT_TYPE content = CONTENT_NONE); + static bool Show(ADDON::ScraperPtr& scraper, VIDEO::SScanSettings& settings, CONTENT_TYPE content = CONTENT_NONE); + +protected: + // specializations of CGUIWindow + void OnInitWindow() override; + + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + +private: + void SetLabel2(const std::string &settingid, const std::string &label); + void ToggleState(const std::string &settingid, bool enabled); + using CGUIDialogSettingsManualBase::SetFocus; + void SetFocusToSetting(const std::string& settingid); + + /*! + * @brief The currently selected content type + */ + CONTENT_TYPE m_content = CONTENT_NONE; + /*! + * @brief The selected content type at dialog creation + */ + CONTENT_TYPE m_originalContent = CONTENT_NONE; + /*! + * @brief The currently selected scraper + */ + ADDON::ScraperPtr m_scraper; + + bool m_showScanSettings = false; + bool m_scanRecursive = false; + bool m_useDirectoryNames = false; + bool m_containsSingleItem = false; + bool m_exclude = false; + bool m_noUpdating = false; + bool m_allExternalAudio = false; +}; diff --git a/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp new file mode 100644 index 0000000..7e88553 --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogLibExportSettings.cpp @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogLibExportSettings.h" + +#include "ServiceBroker.h" +#include "Util.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "filesystem/Directory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/helpers/DialogHelper.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "settings/SettingUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/windows/GUIControlSettings.h" +#include "storage/MediaManager.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +#include <limits.h> +#include <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +using namespace ADDON; +using namespace KODI::MESSAGING; + +using KODI::MESSAGING::HELPERS::DialogResponse; + +CGUIDialogLibExportSettings::CGUIDialogLibExportSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_LIBEXPORT_SETTINGS, "DialogSettings.xml") +{ } + +bool CGUIDialogLibExportSettings::Show(CLibExportSettings& settings) +{ + CGUIDialogLibExportSettings *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogLibExportSettings>(WINDOW_DIALOG_LIBEXPORT_SETTINGS); + if (!dialog) + return false; + + // Get current export settings from service broker + const std::shared_ptr<CSettings> pSettings = CServiceBroker::GetSettingsComponent()->GetSettings(); + dialog->m_settings.SetExportType(pSettings->GetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE)); + dialog->m_settings.m_strPath = pSettings->GetString(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER); + dialog->m_settings.SetItemsToExport(pSettings->GetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS)); + dialog->m_settings.m_unscraped = pSettings->GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED); + dialog->m_settings.m_artwork = pSettings->GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK); + dialog->m_settings.m_skipnfo = pSettings->GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO); + dialog->m_settings.m_overwrite = pSettings->GetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE); + + // Ensure NFO or art output enabled when albums exported (adjust old saved settings) + if (dialog->m_settings.IsItemExported(ELIBEXPORT_ALBUMS) && (dialog->m_settings.m_skipnfo && !dialog->m_settings.m_artwork)) + dialog->m_settings.m_skipnfo = false; + + dialog->m_destinationChecked = false; + dialog->Open(); + + bool confirmed = dialog->IsConfirmed(); + if (confirmed) + { + // Return the new settings (saved by service broker but avoids re-reading) + settings = dialog->m_settings; + } + return confirmed; +} + +void CGUIDialogLibExportSettings::OnInitWindow() +{ + CGUIDialogSettingsManualBase::OnInitWindow(); +} + +void CGUIDialogLibExportSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (!setting) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string &settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE) + { + m_settings.SetExportType(std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + SetupView(); + SetFocus(CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER) + { + m_settings.m_strPath = std::static_pointer_cast<const CSettingString>(setting)->GetValue(); + UpdateButtons(); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE) + m_settings.m_overwrite = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS) + { + m_settings.SetItemsToExport(GetExportItemsFromSetting(setting)); + if (m_settings.IsItemExported(ELIBEXPORT_ALBUMS) && (m_settings.m_skipnfo && !m_settings.m_artwork)) + { + m_settings.m_skipnfo = false; + m_settingNFO->SetValue(true); + UpdateToggles(); + } + UpdateDescription(); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK) + { + m_settings.m_artwork = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + if (m_settings.IsItemExported(ELIBEXPORT_ALBUMS) && (m_settings.m_skipnfo && !m_settings.m_artwork)) + { + m_settings.m_skipnfo = false; + m_settingNFO->SetValue(true); + } + UpdateToggles(); + } + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED) + m_settings.m_unscraped = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + else if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO) + { + m_settings.m_skipnfo = !std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + if (m_settings.IsItemExported(ELIBEXPORT_ALBUMS) && (m_settings.m_skipnfo && !m_settings.m_artwork)) + { + m_settings.m_artwork = true; + m_settingArt->SetValue(true); + } + UpdateToggles(); + } +} + +void CGUIDialogLibExportSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingAction(setting); + + const std::string &settingId = setting->GetId(); + + if (settingId == CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER && !m_settings.IsToLibFolders() && + !m_settings.IsArtistFoldersOnly()) + { + VECSOURCES shares; + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + CServiceBroker::GetMediaManager().GetNetworkLocations(shares); + CServiceBroker::GetMediaManager().GetRemovableDrives(shares); + std::string strDirectory = m_settings.m_strPath; + if (!strDirectory.empty()) + { + URIUtils::AddSlashAtEnd(strDirectory); + bool bIsSource; + if (CUtil::GetMatchingSource(strDirectory, shares, bIsSource) < 0) // path is outside shares - add it as a separate one + { + CMediaSource share; + share.strName = g_localizeStrings.Get(13278); + share.strPath = strDirectory; + shares.push_back(share); + } + } + else + strDirectory = "default location"; + + if (CGUIDialogFileBrowser::ShowAndGetDirectory(shares, g_localizeStrings.Get(661), strDirectory, true)) + { + if (!strDirectory.empty()) + { + m_destinationChecked = true; + m_settings.m_strPath = strDirectory; + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, strDirectory); + SetFocus(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER); + } + } + UpdateButtons(); + } +} + +bool CGUIDialogLibExportSettings::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_SETTINGS_OKAY_BUTTON) + { + OnOK(); + return true; + } + } + break; + } + return CGUIDialogSettingsManualBase::OnMessage(message); +} + +void CGUIDialogLibExportSettings::OnOK() +{ + // Validate destination folder + if (m_settings.IsToLibFolders() || m_settings.IsArtistFoldersOnly()) + { + // Check artist info folder setting + std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + if (path.empty()) + { + //"Unable to export to library folders as the system artist information folder setting is empty" + //Settings (YES) button takes user to enter the artist info folder setting + if (HELPERS::ShowYesNoDialogText(20223, 38317, 186, 10004) == DialogResponse::CHOICE_YES) + { + m_confirmed = false; + Close(); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SETTINGS_MEDIA, CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER); + } + return; + } + } + else if (!m_destinationChecked) + { + // ELIBEXPORT_SINGLEFILE or LIBEXPORT_SEPARATEFILES + // Check that destination folder exists + if (!XFILE::CDirectory::Exists(m_settings.m_strPath)) + { + HELPERS::ShowOKDialogText(CVariant{ 38300 }, CVariant{ 38318 }); + return; + } + } + m_confirmed = true; + Save(); + Close(); +} + +bool CGUIDialogLibExportSettings::Save() +{ + CLog::Log(LOGINFO, "CGUIDialogMusicExportSettings: Save() called"); + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + settings->SetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE, m_settings.GetExportType()); + settings->SetString(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, m_settings.m_strPath); + settings->SetInt(CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS, m_settings.GetItemsToExport()); + settings->SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, m_settings.m_unscraped); + settings->SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, m_settings.m_overwrite); + settings->SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, m_settings.m_artwork); + settings->SetBool(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, m_settings.m_skipnfo); + settings->Save(); + + return true; +} + +void CGUIDialogLibExportSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + SetHeading(38300); + + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_OKAY_BUTTON, 38319); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 222); + + UpdateButtons(); + UpdateToggles(); + UpdateDescription(); +} + +void CGUIDialogLibExportSettings::UpdateButtons() +{ + // Enable Export button when destination folder has a path (but may not exist) + bool enableExport(true); + if (m_settings.IsSingleFile() || + m_settings.IsSeparateFiles()) + enableExport = !m_settings.m_strPath.empty(); + + CONTROL_ENABLE_ON_CONDITION(CONTROL_SETTINGS_OKAY_BUTTON, enableExport); + if (!enableExport) + SetFocus(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER); +} + +void CGUIDialogLibExportSettings::UpdateToggles() +{ + if (m_settings.IsSeparateFiles()) + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, !m_settings.m_skipnfo); + + if (!m_settings.IsItemExported(ELIBEXPORT_ALBUMS) && m_settings.m_skipnfo && !m_settings.m_artwork) + { + //"Output information to NFO files (currently exporting artist folders only)" + SetLabel(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, g_localizeStrings.Get(38310)); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, false); + } + else + { + //"Output information to NFO files" + SetLabel(CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, g_localizeStrings.Get(38309)); + ToggleState(CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, true); + } +} + +void CGUIDialogLibExportSettings::UpdateDescription() +{ + if (m_settings.IsToLibFolders()) + { + // Destination button is description of what to library means + SetLabel(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, ""); + if (m_settings.IsItemExported(ELIBEXPORT_ALBUMS)) + if (m_settings.IsArtists()) + //"Artists exported to Artist Information Folder and albums to music folders" + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, g_localizeStrings.Get(38322)); + else + //"Albums exported to music folders" + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, g_localizeStrings.Get(38323)); + else + // "Artists exported to Artist Information Folder" + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, g_localizeStrings.Get(38324)); + } + else if (m_settings.IsArtistFoldersOnly()) + { + // Destination button is description of what artist folders means + SetLabel(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, ""); + //"Artists folders created in Artist Information Folder" + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, g_localizeStrings.Get(38325)); + } + else + { + SetLabel2(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, m_settings.m_strPath); + SetLabel(CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, g_localizeStrings.Get(38305)); + } +} + +void CGUIDialogLibExportSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + std::shared_ptr<CSettingCategory> category = AddCategory("exportsettings", -1); + if (!category) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings: unable to setup settings"); + return; + } + + std::shared_ptr<CSettingGroup> groupDetails = AddGroup(category); + if (!groupDetails) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings: unable to setup settings"); + return; + } + + TranslatableIntegerSettingOptions entries; + + entries.push_back(TranslatableIntegerSettingOption(38301, ELIBEXPORT_SINGLEFILE)); + entries.push_back(TranslatableIntegerSettingOption(38303, ELIBEXPORT_TOLIBRARYFOLDER)); + entries.push_back(TranslatableIntegerSettingOption(38302, ELIBEXPORT_SEPARATEFILES)); + entries.push_back(TranslatableIntegerSettingOption(38321, ELIBEXPORT_ARTISTFOLDERS)); + AddList(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_FILETYPE, 38304, SettingLevel::Basic, m_settings.GetExportType(), entries, 38304); // "Choose kind of export output" + AddButton(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_FOLDER, 38305, SettingLevel::Basic); + + entries.clear(); + if (!m_settings.IsArtistFoldersOnly()) + entries.push_back(TranslatableIntegerSettingOption(132, ELIBEXPORT_ALBUMS)); //ablums + if (m_settings.IsSingleFile()) + entries.push_back(TranslatableIntegerSettingOption(134, ELIBEXPORT_SONGS)); //songs + entries.push_back( + TranslatableIntegerSettingOption(38043, ELIBEXPORT_ALBUMARTISTS)); //album artists + entries.push_back(TranslatableIntegerSettingOption(38312, ELIBEXPORT_SONGARTISTS)); //song artists + entries.push_back( + TranslatableIntegerSettingOption(38313, ELIBEXPORT_OTHERARTISTS)); //other artists + + std::vector<int> items; + if (m_settings.IsArtistFoldersOnly()) + { + // Only artists, not albums, at least album artists + items = m_settings.GetLimitedItems(ELIBEXPORT_ALBUMARTISTS + ELIBEXPORT_SONGARTISTS + ELIBEXPORT_OTHERARTISTS); + if (items.size() == 0) + items.emplace_back(ELIBEXPORT_ALBUMARTISTS); + } + else if (!m_settings.IsSingleFile()) + { + // No songs unless single file export, at least album artists + items = m_settings.GetLimitedItems(ELIBEXPORT_ALBUMS + ELIBEXPORT_ALBUMARTISTS + ELIBEXPORT_SONGARTISTS + ELIBEXPORT_OTHERARTISTS); + if (items.size() == 0) + items.emplace_back(ELIBEXPORT_ALBUMARTISTS); + } + else + items = m_settings.GetExportItems(); + + AddList(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ITEMS, 38306, SettingLevel::Basic, items, entries, 133, 1); + + if (m_settings.IsToLibFolders() || m_settings.IsSeparateFiles()) + { + m_settingNFO = AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_SKIPNFO, 38309, SettingLevel::Basic, !m_settings.m_skipnfo); + if (m_settings.IsSeparateFiles()) + AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_UNSCRAPED, 38308, SettingLevel::Basic, m_settings.m_unscraped); + m_settingArt = AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_ARTWORK, 38307, SettingLevel::Basic, m_settings.m_artwork); + AddToggle(groupDetails, CSettings::SETTING_MUSICLIBRARY_EXPORT_OVERWRITE, 38311, SettingLevel::Basic, m_settings.m_overwrite); + } +} + +void CGUIDialogLibExportSettings::SetLabel2(const std::string &settingid, const std::string &label) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != nullptr && settingControl->GetControl() != nullptr) + SET_CONTROL_LABEL2(settingControl->GetID(), label); +} + +void CGUIDialogLibExportSettings::SetLabel(const std::string &settingid, const std::string &label) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != nullptr && settingControl->GetControl() != nullptr) + SetControlLabel(settingControl->GetID(), label); +} + +void CGUIDialogLibExportSettings::ToggleState(const std::string & settingid, bool enabled) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != nullptr && settingControl->GetControl() != nullptr) + { + if (enabled) + CONTROL_ENABLE(settingControl->GetID()); + else + CONTROL_DISABLE(settingControl->GetID()); + } +} + +void CGUIDialogLibExportSettings::SetFocus(const std::string &settingid) +{ + BaseSettingControlPtr settingControl = GetSettingControl(settingid); + if (settingControl != NULL && settingControl->GetControl() != NULL) + SET_CONTROL_FOCUS(settingControl->GetID(), 0); +} + +int CGUIDialogLibExportSettings::GetExportItemsFromSetting(const SettingConstPtr& setting) +{ + std::shared_ptr<const CSettingList> settingList = std::static_pointer_cast<const CSettingList>(setting); + if (settingList->GetElementType() != SettingType::Integer) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings::{} - wrong items element type", __FUNCTION__); + return 0; + } + int exportitems = 0; + std::vector<CVariant> list = CSettingUtils::GetList(settingList); + for (const auto &value : list) + { + if (!value.isInteger()) + { + CLog::Log(LOGERROR, "CGUIDialogLibExportSettings::{} - wrong items value type", __FUNCTION__); + return 0; + } + exportitems += static_cast<int>(value.asInteger()); + } + return exportitems; +} diff --git a/xbmc/settings/dialogs/GUIDialogLibExportSettings.h b/xbmc/settings/dialogs/GUIDialogLibExportSettings.h new file mode 100644 index 0000000..4c861ba --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogLibExportSettings.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/LibExportSettings.h" +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +#include <map> + +class CGUIDialogLibExportSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogLibExportSettings(); + + // specialization of CGUIWindow + bool HasListItems() const override { return true; } + static bool Show(CLibExportSettings& settings); + +protected: + // specializations of CGUIWindow + void OnInitWindow() override; + + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool OnMessage(CGUIMessage& message) override; + bool AllowResettingSettings() const override { return false; } + bool Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + + void OnOK(); + void UpdateButtons(); + +private: + void SetLabel2(const std::string &settingid, const std::string &label); + void SetLabel(const std::string &settingid, const std::string &label); + void ToggleState(const std::string &settingid, bool enabled); + + using CGUIDialogSettingsManualBase::SetFocus; + void SetFocus(const std::string &settingid); + static int GetExportItemsFromSetting(const SettingConstPtr& setting); + void UpdateToggles(); + void UpdateDescription(); + + CLibExportSettings m_settings; + bool m_destinationChecked = false; + std::shared_ptr<CSettingBool> m_settingNFO; + std::shared_ptr<CSettingBool> m_settingArt; +}; diff --git a/xbmc/settings/dialogs/GUIDialogSettingsBase.cpp b/xbmc/settings/dialogs/GUIDialogSettingsBase.cpp new file mode 100644 index 0000000..32d3958 --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogSettingsBase.cpp @@ -0,0 +1,972 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSettingsBase.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIColorButtonControl.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIControlGroupList.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUIImage.h" +#include "guilib/GUILabelControl.h" +#include "guilib/GUIRadioButtonControl.h" +#include "guilib/GUISettingsSliderControl.h" +#include "guilib/GUISpinControlEx.h" +#include "guilib/GUIToggleButtonControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "settings/SettingControl.h" +#include "settings/lib/SettingSection.h" +#include "settings/windows/GUIControlSettings.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <set> +#include <string> +#include <vector> + +#if defined(TARGET_WINDOWS) // disable 4355: 'this' used in base member initializer list +#pragma warning(push) +#pragma warning(disable : 4355) +#endif // defined(TARGET_WINDOWS) + +#define CATEGORY_GROUP_ID 3 +#define SETTINGS_GROUP_ID 5 + +#define CONTROL_DEFAULT_BUTTON 7 +#define CONTROL_DEFAULT_RADIOBUTTON 8 +#define CONTROL_DEFAULT_SPIN 9 +#define CONTROL_DEFAULT_CATEGORY_BUTTON 10 +#define CONTROL_DEFAULT_SEPARATOR 11 +#define CONTROL_DEFAULT_EDIT 12 +#define CONTROL_DEFAULT_SLIDER 13 +#define CONTROL_DEFAULT_SETTING_LABEL 14 +#define CONTROL_DEFAULT_COLORBUTTON 15 + +CGUIDialogSettingsBase::CGUIDialogSettingsBase(int windowId, const std::string& xmlFile) + : CGUIDialog(windowId, xmlFile), + m_iSetting(0), + m_iCategory(0), + m_resetSetting(NULL), + m_dummyCategory(NULL), + m_pOriginalSpin(NULL), + m_pOriginalSlider(NULL), + m_pOriginalRadioButton(NULL), + m_pOriginalColorButton(nullptr), + m_pOriginalCategoryButton(NULL), + m_pOriginalButton(NULL), + m_pOriginalEdit(NULL), + m_pOriginalImage(NULL), + m_pOriginalGroupTitle(NULL), + m_newOriginalEdit(false), + m_delayedTimer(this), + m_confirmed(false), + m_focusedControl(0), + m_fadedControl(0) +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogSettingsBase::~CGUIDialogSettingsBase() +{ + FreeControls(); + DeleteControls(); +} + +bool CGUIDialogSettingsBase::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + m_delayedSetting.reset(); + if (message.GetParam1() != WINDOW_INVALID) + { // coming to this window first time (ie not returning back from some other window) + // so we reset our section and control states + m_iCategory = 0; + ResetControlStates(); + } + + if (AllowResettingSettings()) + { + m_resetSetting = std::make_shared<CSettingAction>(SETTINGS_RESET_SETTING_ID); + m_resetSetting->SetLabel(10041); + m_resetSetting->SetHelp(10045); + m_resetSetting->SetControl(CreateControl("button")); + } + + m_dummyCategory = std::make_shared<CSettingCategory>(SETTINGS_EMPTY_CATEGORY_ID); + m_dummyCategory->SetLabel(10046); + m_dummyCategory->SetHelp(10047); + break; + } + + case GUI_MSG_WINDOW_DEINIT: + { + // cancel any delayed changes + if (m_delayedSetting != NULL) + { + m_delayedTimer.Stop(); + CGUIMessage message(GUI_MSG_UPDATE_ITEM, GetID(), m_delayedSetting->GetID()); + OnMessage(message); + } + + CGUIDialog::OnMessage(message); + FreeControls(); + return true; + } + + case GUI_MSG_FOCUSED: + { + CGUIDialog::OnMessage(message); + m_focusedControl = GetFocusedControlID(); + + // cancel any delayed changes + if (m_delayedSetting != NULL && m_delayedSetting->GetID() != m_focusedControl) + { + m_delayedTimer.Stop(); + // param1 = 1 for "reset the control if it's invalid" + CGUIMessage message(GUI_MSG_UPDATE_ITEM, GetID(), m_delayedSetting->GetID(), 1); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID()); + } + // update the value of the previous setting (in case it was invalid) + else if (m_iSetting >= CONTROL_SETTINGS_START_CONTROL && + m_iSetting < (int)(CONTROL_SETTINGS_START_CONTROL + m_settingControls.size())) + { + BaseSettingControlPtr control = GetSettingControl(m_iSetting); + if (control != NULL && control->GetSetting() != NULL && !control->IsValid()) + { + // param1 = 1 for "reset the control if it's invalid" + // param2 = 1 for "only update the current value" + CGUIMessage message(GUI_MSG_UPDATE_ITEM, GetID(), m_iSetting, 1, 1); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID()); + } + } + + CVariant description; + + // check if we have changed the category and need to create new setting controls + if (m_focusedControl >= CONTROL_SETTINGS_START_BUTTONS && + m_focusedControl < (int)(CONTROL_SETTINGS_START_BUTTONS + m_categories.size())) + { + int categoryIndex = m_focusedControl - CONTROL_SETTINGS_START_BUTTONS; + SettingCategoryPtr category = m_categories.at(categoryIndex); + if (categoryIndex != m_iCategory) + { + if (!category->CanAccess()) + { + // unable to go to this category - focus the previous one + SET_CONTROL_FOCUS(CONTROL_SETTINGS_START_BUTTONS + m_iCategory, 0); + return false; + } + + m_iCategory = categoryIndex; + CreateSettings(); + } + + description = category->GetHelp(); + } + else if (m_focusedControl >= CONTROL_SETTINGS_START_CONTROL && + m_focusedControl < (int)(CONTROL_SETTINGS_START_CONTROL + m_settingControls.size())) + { + m_iSetting = m_focusedControl; + std::shared_ptr<CSetting> setting = GetSettingControl(m_focusedControl)->GetSetting(); + if (setting != NULL) + description = setting->GetHelp(); + } + + // set the description of the currently focused category/setting + if (description.isInteger() || (description.isString() && !description.empty())) + SetDescription(description); + + return true; + } + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_SETTINGS_OKAY_BUTTON) + { + if (OnOkay()) + { + Close(); + return true; + } + + return false; + } + + if (iControl == CONTROL_SETTINGS_CANCEL_BUTTON) + { + OnCancel(); + Close(); + return true; + } + + BaseSettingControlPtr control = GetSettingControl(iControl); + if (control != NULL) + OnClick(control); + + break; + } + + case GUI_MSG_UPDATE_ITEM: + { + if (m_delayedSetting != NULL && m_delayedSetting->GetID() == message.GetControlId()) + { + // first get the delayed setting and reset its member variable + // to avoid handling the delayed setting twice in case the OnClick() + // performed later causes the window to be deinitialized (e.g. when + // changing the language) + BaseSettingControlPtr delayedSetting = m_delayedSetting; + m_delayedSetting.reset(); + + // if updating the setting fails and param1 has been specifically set + // we need to call OnSettingChanged() to restore a valid value in the + // setting control + if (!delayedSetting->OnClick() && message.GetParam1() != 0) + OnSettingChanged(delayedSetting->GetSetting()); + return true; + } + + if (message.GetControlId() >= CONTROL_SETTINGS_START_CONTROL && + message.GetControlId() < (int)(CONTROL_SETTINGS_START_CONTROL + m_settingControls.size())) + { + BaseSettingControlPtr settingControl = GetSettingControl(message.GetControlId()); + if (settingControl.get() != NULL && settingControl->GetSetting() != NULL) + { + settingControl->UpdateFromSetting(message.GetParam2() != 0); + return true; + } + } + break; + } + + case GUI_MSG_UPDATE: + { + if (IsActive() && HasID(message.GetSenderId())) + { + int focusedControl = GetFocusedControlID(); + CreateSettings(); + SET_CONTROL_FOCUS(focusedControl, 0); + } + break; + } + + default: + break; + } + + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogSettingsBase::OnAction(const CAction& action) +{ + switch (action.GetID()) + { + case ACTION_SETTINGS_RESET: + { + OnResetSettings(); + return true; + } + + case ACTION_DELETE_ITEM: + { + if (m_iSetting >= CONTROL_SETTINGS_START_CONTROL && + m_iSetting < (int)(CONTROL_SETTINGS_START_CONTROL + m_settingControls.size())) + { + auto settingControl = GetSettingControl(m_iSetting); + if (settingControl != nullptr) + { + std::shared_ptr<CSetting> setting = settingControl->GetSetting(); + if (setting != nullptr) + { + setting->Reset(); + return true; + } + } + } + break; + } + + default: + break; + } + + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogSettingsBase::OnBack(int actionID) +{ + m_lastControlID = 0; // don't save the control as we go to a different window each time + + // if the setting dialog is not a window but a dialog we need to close differently + if (!IsDialog()) + return CGUIWindow::OnBack(actionID); + + return CGUIDialog::OnBack(actionID); +} + +void CGUIDialogSettingsBase::DoProcess(unsigned int currentTime, CDirtyRegionList& dirtyregions) +{ + // update alpha status of current button + CGUIControl* control = GetFirstFocusableControl(CONTROL_SETTINGS_START_BUTTONS + m_iCategory); + if (control) + { + if (m_fadedControl && + (m_fadedControl != control->GetID() || m_fadedControl == m_focusedControl)) + { + if (control->GetControlType() == CGUIControl::GUICONTROL_BUTTON) + static_cast<CGUIButtonControl*>(control)->SetAlpha(0xFF); + else + static_cast<CGUIButtonControl*>(control)->SetSelected(false); + m_fadedControl = 0; + } + + if (!control->HasFocus()) + { + m_fadedControl = control->GetID(); + if (control->GetControlType() == CGUIControl::GUICONTROL_BUTTON) + static_cast<CGUIButtonControl*>(control)->SetAlpha(0x80); + else if (control->GetControlType() == CGUIControl::GUICONTROL_TOGGLEBUTTON) + static_cast<CGUIButtonControl*>(control)->SetSelected(true); + else + m_fadedControl = 0; + + if (m_fadedControl) + control->SetFocus(true); + } + } + CGUIDialog::DoProcess(currentTime, dirtyregions); +} + +void CGUIDialogSettingsBase::OnInitWindow() +{ + m_confirmed = false; + SetupView(); + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogSettingsBase::SetupControls(bool createSettings /* = true */) +{ + // cleanup first, if necessary + FreeControls(); + + // get all controls + m_pOriginalSpin = dynamic_cast<CGUISpinControlEx*>(GetControl(CONTROL_DEFAULT_SPIN)); + m_pOriginalSlider = dynamic_cast<CGUISettingsSliderControl*>(GetControl(CONTROL_DEFAULT_SLIDER)); + m_pOriginalRadioButton = + dynamic_cast<CGUIRadioButtonControl*>(GetControl(CONTROL_DEFAULT_RADIOBUTTON)); + m_pOriginalCategoryButton = + dynamic_cast<CGUIButtonControl*>(GetControl(CONTROL_DEFAULT_CATEGORY_BUTTON)); + m_pOriginalButton = dynamic_cast<CGUIButtonControl*>(GetControl(CONTROL_DEFAULT_BUTTON)); + m_pOriginalImage = dynamic_cast<CGUIImage*>(GetControl(CONTROL_DEFAULT_SEPARATOR)); + m_pOriginalEdit = dynamic_cast<CGUIEditControl*>(GetControl(CONTROL_DEFAULT_EDIT)); + m_pOriginalGroupTitle = + dynamic_cast<CGUILabelControl*>(GetControl(CONTROL_DEFAULT_SETTING_LABEL)); + m_pOriginalColorButton = + dynamic_cast<CGUIColorButtonControl*>(GetControl(CONTROL_DEFAULT_COLORBUTTON)); + + // if there's no edit control but there's a button control use that instead + if (m_pOriginalEdit == nullptr && m_pOriginalButton != nullptr) + { + m_pOriginalEdit = new CGUIEditControl(*m_pOriginalButton); + m_newOriginalEdit = true; + } + + // hide all default controls by default + if (m_pOriginalSpin != nullptr) + m_pOriginalSpin->SetVisible(false); + if (m_pOriginalSlider != nullptr) + m_pOriginalSlider->SetVisible(false); + if (m_pOriginalRadioButton != nullptr) + m_pOriginalRadioButton->SetVisible(false); + if (m_pOriginalButton != nullptr) + m_pOriginalButton->SetVisible(false); + if (m_pOriginalCategoryButton != nullptr) + m_pOriginalCategoryButton->SetVisible(false); + if (m_pOriginalEdit != nullptr) + m_pOriginalEdit->SetVisible(false); + if (m_pOriginalImage != nullptr) + m_pOriginalImage->SetVisible(false); + if (m_pOriginalGroupTitle != nullptr) + m_pOriginalGroupTitle->SetVisible(false); + if (m_pOriginalColorButton != nullptr) + m_pOriginalColorButton->SetVisible(false); + + // get the section + SettingSectionPtr section = GetSection(); + if (section == NULL) + return; + + // update the screen string + if (section->GetLabel() >= 0) + SetHeading(section->GetLabel()); + + // get the categories we need + m_categories = section->GetCategories((SettingLevel)GetSettingLevel()); + if (m_categories.empty()) + m_categories.push_back(m_dummyCategory); + + if (m_pOriginalCategoryButton != NULL) + { + // setup our control groups... + CGUIControlGroupList* group = + dynamic_cast<CGUIControlGroupList*>(GetControl(CATEGORY_GROUP_ID)); + if (!group) + return; + + // go through the categories and create the necessary buttons + int buttonIdOffset = 0; + for (SettingCategoryList::const_iterator category = m_categories.begin(); + category != m_categories.end(); ++category) + { + CGUIButtonControl* pButton = NULL; + if (m_pOriginalCategoryButton->GetControlType() == CGUIControl::GUICONTROL_TOGGLEBUTTON) + pButton = new CGUIToggleButtonControl( + *static_cast<CGUIToggleButtonControl*>(m_pOriginalCategoryButton)); + else if (m_pOriginalCategoryButton->GetControlType() == CGUIControl::GUICONTROL_COLORBUTTON) + pButton = new CGUIColorButtonControl( + *static_cast<CGUIColorButtonControl*>(m_pOriginalCategoryButton)); + else + pButton = new CGUIButtonControl(*m_pOriginalCategoryButton); + pButton->SetLabel(GetSettingsLabel(*category)); + pButton->SetID(CONTROL_SETTINGS_START_BUTTONS + buttonIdOffset); + pButton->SetVisible(true); + pButton->AllocResources(); + + group->AddControl(pButton); + buttonIdOffset++; + } + } + + if (createSettings) + CreateSettings(); + + // set focus correctly depending on whether there are categories visible or not + if (m_pOriginalCategoryButton == NULL && + (m_defaultControl <= 0 || m_defaultControl == CATEGORY_GROUP_ID)) + m_defaultControl = SETTINGS_GROUP_ID; + else if (m_pOriginalCategoryButton != NULL && m_defaultControl <= 0) + m_defaultControl = CATEGORY_GROUP_ID; +} + +void CGUIDialogSettingsBase::FreeControls() +{ + // clear the category group + CGUIControlGroupList* control = + dynamic_cast<CGUIControlGroupList*>(GetControl(CATEGORY_GROUP_ID)); + if (control) + { + control->FreeResources(); + control->ClearAll(); + } + m_categories.clear(); + FreeSettingsControls(); +} + +void CGUIDialogSettingsBase::DeleteControls() +{ + if (m_newOriginalEdit) + { + delete m_pOriginalEdit; + m_pOriginalEdit = NULL; + } + + m_resetSetting.reset(); + m_dummyCategory.reset(); +} + +void CGUIDialogSettingsBase::FreeSettingsControls() +{ + // clear the settings group + CGUIControlGroupList* control = + dynamic_cast<CGUIControlGroupList*>(GetControl(SETTINGS_GROUP_ID)); + if (control) + { + control->FreeResources(); + control->ClearAll(); + } + + for (std::vector<BaseSettingControlPtr>::iterator control = m_settingControls.begin(); + control != m_settingControls.end(); ++control) + (*control)->Clear(); + + m_settingControls.clear(); +} + +void CGUIDialogSettingsBase::OnTimeout() +{ + UpdateSettingControl(m_delayedSetting, true); +} + +void CGUIDialogSettingsBase::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL || setting->GetType() == SettingType::Unknown || + setting->GetType() == SettingType::Action) + return; + + UpdateSettingControl(setting->GetId(), true); +} + +void CGUIDialogSettingsBase::OnSettingPropertyChanged( + const std::shared_ptr<const CSetting>& setting, const char* propertyName) +{ + if (setting == NULL || propertyName == NULL) + return; + + UpdateSettingControl(setting->GetId()); +} + +std::string CGUIDialogSettingsBase::GetLocalizedString(uint32_t labelId) const +{ + return g_localizeStrings.Get(labelId); +} + +void CGUIDialogSettingsBase::SetupView() +{ + SetupControls(); +} + +std::set<std::string> CGUIDialogSettingsBase::CreateSettings() +{ + FreeSettingsControls(); + + std::set<std::string> settingMap; + + if (m_categories.size() <= 0) + return settingMap; + + if (m_iCategory < 0 || m_iCategory >= (int)m_categories.size()) + m_iCategory = 0; + + CGUIControlGroupList* group = dynamic_cast<CGUIControlGroupList*>(GetControl(SETTINGS_GROUP_ID)); + if (group == NULL) + return settingMap; + + SettingCategoryPtr category = m_categories.at(m_iCategory); + if (category == NULL) + return settingMap; + + // set the description of the current category + SetDescription(category->GetHelp()); + + const SettingGroupList& groups = category->GetGroups((SettingLevel)GetSettingLevel()); + int iControlID = CONTROL_SETTINGS_START_CONTROL; + bool first = true; + for (SettingGroupList::const_iterator groupIt = groups.begin(); groupIt != groups.end(); + ++groupIt) + { + if (*groupIt == NULL) + continue; + + const SettingList& settings = (*groupIt)->GetSettings((SettingLevel)GetSettingLevel()); + if (settings.size() <= 0) + continue; + + std::shared_ptr<const CSettingControlTitle> title = + std::dynamic_pointer_cast<const CSettingControlTitle>((*groupIt)->GetControl()); + bool hideSeparator = title ? title->IsSeparatorHidden() : false; + bool separatorBelowGroupLabel = title ? title->IsSeparatorBelowLabel() : false; + int groupLabel = (*groupIt)->GetLabel(); + + // hide the separator for the first settings grouplist if it + // is the very first item in the list (also above the label) + if (first) + { + first = false; + if (groupLabel <= 0) + hideSeparator = true; + } + else if (!separatorBelowGroupLabel && !hideSeparator) + AddSeparator(group->GetWidth(), iControlID); + + if (groupLabel > 0) + AddGroupLabel(*groupIt, group->GetWidth(), iControlID); + + if (separatorBelowGroupLabel && !hideSeparator) + AddSeparator(group->GetWidth(), iControlID); + + for (SettingList::const_iterator settingIt = settings.begin(); settingIt != settings.end(); + ++settingIt) + { + const std::shared_ptr<CSetting>& pSetting = *settingIt; + settingMap.insert(pSetting->GetId()); + AddSetting(pSetting, group->GetWidth(), iControlID); + } + } + + if (AllowResettingSettings() && !settingMap.empty()) + { + // add "Reset" control + AddSeparator(group->GetWidth(), iControlID); + AddSetting(m_resetSetting, group->GetWidth(), iControlID); + } + + // update our settings (turns controls on/off as appropriate) + UpdateSettings(); + + group->SetInvalid(); + + return settingMap; +} + +std::string CGUIDialogSettingsBase::GetSettingsLabel(const std::shared_ptr<ISetting>& pSetting) +{ + return GetLocalizedString(pSetting->GetLabel()); +} + +void CGUIDialogSettingsBase::UpdateSettings() +{ + for (std::vector<BaseSettingControlPtr>::iterator it = m_settingControls.begin(); + it != m_settingControls.end(); ++it) + { + BaseSettingControlPtr pSettingControl = *it; + std::shared_ptr<CSetting> pSetting = pSettingControl->GetSetting(); + CGUIControl* pControl = pSettingControl->GetControl(); + if (pSetting == NULL || pControl == NULL) + continue; + + pSettingControl->UpdateFromSetting(); + } +} + +CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr<CSetting>& pSetting, + float width, + int& iControlID) +{ + if (pSetting == NULL) + return NULL; + + BaseSettingControlPtr pSettingControl; + CGUIControl* pControl = NULL; + + // determine the label and any possible indentation in case of sub settings + std::string label = GetSettingsLabel(pSetting); + int parentLevels = 0; + std::shared_ptr<CSetting> parentSetting = GetSetting(pSetting->GetParent()); + while (parentSetting != NULL) + { + parentLevels++; + parentSetting = GetSetting(parentSetting->GetParent()); + } + + if (parentLevels > 0) + { + // add additional 2 spaces indentation for anything past one level + std::string indentation; + for (int index = 1; index < parentLevels; index++) + indentation.append(" "); + label = StringUtils::Format(g_localizeStrings.Get(168), indentation, label); + } + + // create the proper controls + if (!pSetting->GetControl()) + return NULL; + + std::string controlType = pSetting->GetControl()->GetType(); + if (controlType == "toggle") + { + if (m_pOriginalRadioButton != NULL) + pControl = m_pOriginalRadioButton->Clone(); + if (pControl == NULL) + return NULL; + + static_cast<CGUIRadioButtonControl*>(pControl)->SetLabel(label); + pSettingControl.reset(new CGUIControlRadioButtonSetting( + static_cast<CGUIRadioButtonControl*>(pControl), iControlID, pSetting, this)); + } + else if (controlType == "spinner") + { + if (m_pOriginalSpin != NULL) + pControl = new CGUISpinControlEx(*m_pOriginalSpin); + if (pControl == NULL) + return NULL; + + static_cast<CGUISpinControlEx*>(pControl)->SetText(label); + pSettingControl.reset(new CGUIControlSpinExSetting(static_cast<CGUISpinControlEx*>(pControl), + iControlID, pSetting, this)); + } + else if (controlType == "edit") + { + if (m_pOriginalEdit != NULL) + pControl = new CGUIEditControl(*m_pOriginalEdit); + if (pControl == NULL) + return NULL; + + static_cast<CGUIEditControl*>(pControl)->SetLabel(label); + pSettingControl.reset(new CGUIControlEditSetting(static_cast<CGUIEditControl*>(pControl), + iControlID, pSetting, this)); + } + else if (controlType == "list") + { + if (m_pOriginalButton != NULL) + pControl = new CGUIButtonControl(*m_pOriginalButton); + if (pControl == NULL) + return NULL; + + static_cast<CGUIButtonControl*>(pControl)->SetLabel(label); + pSettingControl.reset(new CGUIControlListSetting(static_cast<CGUIButtonControl*>(pControl), + iControlID, pSetting, this)); + } + else if (controlType == "button" || controlType == "slider") + { + if (controlType == "button" || + std::static_pointer_cast<const CSettingControlSlider>(pSetting->GetControl())->UsePopup()) + { + if (m_pOriginalButton != NULL) + pControl = new CGUIButtonControl(*m_pOriginalButton); + if (pControl == NULL) + return NULL; + + static_cast<CGUIButtonControl*>(pControl)->SetLabel(label); + pSettingControl.reset(new CGUIControlButtonSetting(static_cast<CGUIButtonControl*>(pControl), + iControlID, pSetting, this)); + } + else + { + if (m_pOriginalSlider != NULL) + pControl = m_pOriginalSlider->Clone(); + if (pControl == NULL) + return NULL; + + static_cast<CGUISettingsSliderControl*>(pControl)->SetText(label); + pSettingControl.reset(new CGUIControlSliderSetting( + static_cast<CGUISettingsSliderControl*>(pControl), iControlID, pSetting, this)); + } + } + else if (controlType == "range") + { + if (m_pOriginalSlider != NULL) + pControl = m_pOriginalSlider->Clone(); + if (pControl == NULL) + return NULL; + + static_cast<CGUISettingsSliderControl*>(pControl)->SetText(label); + pSettingControl.reset(new CGUIControlRangeSetting( + static_cast<CGUISettingsSliderControl*>(pControl), iControlID, pSetting, this)); + } + else if (controlType == "label") + { + if (m_pOriginalButton != NULL) + pControl = new CGUIButtonControl(*m_pOriginalButton); + if (pControl == NULL) + return NULL; + + static_cast<CGUIButtonControl*>(pControl)->SetLabel(label); + pSettingControl.reset(new CGUIControlLabelSetting(static_cast<CGUIButtonControl*>(pControl), + iControlID, pSetting, this)); + } + else if (controlType == "colorbutton") + { + if (m_pOriginalColorButton) + pControl = m_pOriginalColorButton->Clone(); + if (pControl == nullptr) + return nullptr; + + static_cast<CGUIColorButtonControl*>(pControl)->SetLabel(label); + pSettingControl.reset(new CGUIControlColorButtonSetting( + static_cast<CGUIColorButtonControl*>(pControl), iControlID, pSetting, this)); + } + else + return nullptr; + + if (pSetting->GetControl()->GetDelayed()) + pSettingControl->SetDelayed(); + + return AddSettingControl(pControl, pSettingControl, width, iControlID); +} + +CGUIControl* CGUIDialogSettingsBase::AddSeparator(float width, int& iControlID) +{ + if (m_pOriginalImage == NULL) + return NULL; + + CGUIControl* pControl = new CGUIImage(*m_pOriginalImage); + if (pControl == NULL) + return NULL; + + return AddSettingControl(pControl, + BaseSettingControlPtr(new CGUIControlSeparatorSetting( + static_cast<CGUIImage*>(pControl), iControlID, this)), + width, iControlID); +} + +CGUIControl* CGUIDialogSettingsBase::AddGroupLabel(const std::shared_ptr<CSettingGroup>& group, + float width, + int& iControlID) +{ + if (m_pOriginalGroupTitle == NULL) + return NULL; + + CGUIControl* pControl = new CGUILabelControl(*m_pOriginalGroupTitle); + if (pControl == NULL) + return NULL; + + static_cast<CGUILabelControl*>(pControl)->SetLabel(GetSettingsLabel(group)); + + return AddSettingControl(pControl, + BaseSettingControlPtr(new CGUIControlGroupTitleSetting( + static_cast<CGUILabelControl*>(pControl), iControlID, this)), + width, iControlID); +} + +CGUIControl* CGUIDialogSettingsBase::AddSettingControl(CGUIControl* pControl, + BaseSettingControlPtr pSettingControl, + float width, + int& iControlID) +{ + if (pControl == NULL) + { + pSettingControl.reset(); + return NULL; + } + + pControl->SetID(iControlID++); + pControl->SetVisible(true); + pControl->SetWidth(width); + + CGUIControlGroupList* group = dynamic_cast<CGUIControlGroupList*>(GetControl(SETTINGS_GROUP_ID)); + if (group != NULL) + { + pControl->AllocResources(); + group->AddControl(pControl); + } + m_settingControls.push_back(pSettingControl); + + return pControl; +} + +void CGUIDialogSettingsBase::SetHeading(const CVariant& label) +{ + SetControlLabel(CONTROL_SETTINGS_LABEL, label); +} + +void CGUIDialogSettingsBase::SetDescription(const CVariant& label) +{ + SetControlLabel(CONTROL_SETTINGS_DESCRIPTION, label); +} + +void CGUIDialogSettingsBase::OnResetSettings() +{ + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{10041}, CVariant{10042})) + { + for (std::vector<BaseSettingControlPtr>::iterator it = m_settingControls.begin(); + it != m_settingControls.end(); ++it) + { + std::shared_ptr<CSetting> setting = (*it)->GetSetting(); + if (setting != NULL) + setting->Reset(); + } + } +} + +void CGUIDialogSettingsBase::OnClick(const BaseSettingControlPtr& pSettingControl) +{ + if (AllowResettingSettings() && + pSettingControl->GetSetting()->GetId() == SETTINGS_RESET_SETTING_ID) + { + OnAction(CAction(ACTION_SETTINGS_RESET)); + return; + } + + // we need to first set the delayed setting and then execute OnClick() + // because OnClick() triggers OnSettingChanged() and there we need to + // know if the changed setting is delayed or not + if (pSettingControl->IsDelayed()) + { + m_delayedSetting = pSettingControl; + // for some controls we need to update its displayed data/text before + // OnClick() is called after the delay timer has expired because + // otherwise the displayed value of the control does not match with + // the user's interaction + pSettingControl->UpdateFromControl(); + + // either start or restart the delay timer which will result in a call to + // the control's OnClick() method to update the setting's value + if (m_delayedTimer.IsRunning()) + m_delayedTimer.Restart(); + else + m_delayedTimer.Start(GetDelayMs()); + + return; + } + + // if changing the setting fails + // we need to restore the proper state + if (!pSettingControl->OnClick()) + pSettingControl->UpdateFromSetting(); +} + +void CGUIDialogSettingsBase::UpdateSettingControl(const std::string& settingId, + bool updateDisplayOnly /* = false */) +{ + if (settingId.empty()) + return; + + return UpdateSettingControl(GetSettingControl(settingId), updateDisplayOnly); +} + +void CGUIDialogSettingsBase::UpdateSettingControl(const BaseSettingControlPtr& pSettingControl, + bool updateDisplayOnly /* = false */) +{ + if (pSettingControl == NULL) + return; + + // we send a thread message so that it's processed the following frame (some settings won't + // like being changed during Render()) + // param2 = 1 for "only update the current value" + CGUIMessage message(GUI_MSG_UPDATE_ITEM, GetID(), pSettingControl->GetID(), 0, + updateDisplayOnly ? 1 : 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, GetID()); +} + +void CGUIDialogSettingsBase::SetControlLabel(int controlId, const CVariant& label) +{ + if (GetControl(controlId) == NULL) + return; + + if (label.isString()) + SET_CONTROL_LABEL(controlId, label.asString()); + else if (label.isInteger() && label.asInteger() >= 0) + { + int labelId = static_cast<uint32_t>(label.asInteger()); + std::string localizedString = GetLocalizedString(labelId); + if (!localizedString.empty()) + SET_CONTROL_LABEL(controlId, localizedString); + else + SET_CONTROL_LABEL(controlId, labelId); + } + else + SET_CONTROL_LABEL(controlId, ""); +} + +BaseSettingControlPtr CGUIDialogSettingsBase::GetSettingControl(const std::string& strSetting) +{ + for (std::vector<BaseSettingControlPtr>::iterator control = m_settingControls.begin(); + control != m_settingControls.end(); ++control) + { + if ((*control)->GetSetting() != NULL && (*control)->GetSetting()->GetId() == strSetting) + return *control; + } + + return BaseSettingControlPtr(); +} + +BaseSettingControlPtr CGUIDialogSettingsBase::GetSettingControl(int controlId) +{ + if (controlId < CONTROL_SETTINGS_START_CONTROL || + controlId >= (int)(CONTROL_SETTINGS_START_CONTROL + m_settingControls.size())) + return BaseSettingControlPtr(); + + return m_settingControls[controlId - CONTROL_SETTINGS_START_CONTROL]; +} diff --git a/xbmc/settings/dialogs/GUIDialogSettingsBase.h b/xbmc/settings/dialogs/GUIDialogSettingsBase.h new file mode 100644 index 0000000..a633c65 --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogSettingsBase.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "settings/SettingControl.h" +#include "settings/lib/ISettingCallback.h" +#include "threads/Timer.h" +#include "utils/ILocalizer.h" + +#include <set> +#include <vector> + +#define CONTROL_SETTINGS_LABEL 2 +#define CONTROL_SETTINGS_DESCRIPTION 6 + +#define CONTROL_SETTINGS_OKAY_BUTTON 28 +#define CONTROL_SETTINGS_CANCEL_BUTTON 29 +#define CONTROL_SETTINGS_CUSTOM_BUTTON 30 + +#define CONTROL_SETTINGS_START_BUTTONS -100 +#define CONTROL_SETTINGS_START_CONTROL -80 + +#define SETTINGS_RESET_SETTING_ID "settings.reset" +#define SETTINGS_EMPTY_CATEGORY_ID "categories.empty" + +class CGUIControl; +class CGUIControlBaseSetting; +class CGUIImage; +class CGUISpinControlEx; +class CGUIEditControl; +class CGUIButtonControl; +class CGUIRadioButtonControl; +class CGUISettingsSliderControl; +class CGUILabelControl; +class CGUIColorButtonControl; + +class CSetting; +class CSettingAction; +class CSettingCategory; +class CSettingGroup; +class CSettingSection; + +class CVariant; + +class ISetting; + +typedef std::shared_ptr<CGUIControlBaseSetting> BaseSettingControlPtr; + +class CGUIDialogSettingsBase : public CGUIDialog, + public CSettingControlCreator, + public ILocalizer, + protected ITimerCallback, + protected ISettingCallback +{ +public: + CGUIDialogSettingsBase(int windowId, const std::string& xmlFile); + ~CGUIDialogSettingsBase() override; + + // specializations of CGUIControl + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + bool OnBack(int actionID) override; + void DoProcess(unsigned int currentTime, CDirtyRegionList& dirtyregions) override; + + virtual bool IsConfirmed() const { return m_confirmed; } + + // implementation of ILocalizer + std::string Localize(std::uint32_t code) const override { return GetLocalizedString(code); } + +protected: + // specializations of CGUIWindow + void OnInitWindow() override; + + // implementations of ITimerCallback + void OnTimeout() override; + + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) override; + + // new virtual methods + virtual bool AllowResettingSettings() const { return true; } + virtual int GetSettingLevel() const { return 0; } + virtual std::shared_ptr<CSettingSection> GetSection() = 0; + virtual std::shared_ptr<CSetting> GetSetting(const std::string& settingId) = 0; + virtual std::chrono::milliseconds GetDelayMs() const { return std::chrono::milliseconds(1500); } + virtual std::string GetLocalizedString(uint32_t labelId) const; + + virtual bool OnOkay() + { + m_confirmed = true; + return true; + } + virtual void OnCancel() {} + + virtual void SetupView(); + virtual std::set<std::string> CreateSettings(); + virtual void UpdateSettings(); + + /*! + \brief Get the name for the setting entry + + Used as virtual to allow related settings dialog to give a std::string name of the setting. + If not used on own dialog class it handle the string from int CSetting::GetLabel(), + This must also be used if on related dialog no special entry is wanted. + + \param pSetting Base settings class which need the name + \return Name used on settings dialog + */ + virtual std::string GetSettingsLabel(const std::shared_ptr<ISetting>& pSetting); + + virtual CGUIControl* AddSetting(const std::shared_ptr<CSetting>& pSetting, + float width, + int& iControlID); + virtual CGUIControl* AddSettingControl(CGUIControl* pControl, + BaseSettingControlPtr pSettingControl, + float width, + int& iControlID); + + virtual void SetupControls(bool createSettings = true); + virtual void FreeControls(); + virtual void DeleteControls(); + virtual void FreeSettingsControls(); + + virtual void SetHeading(const CVariant& label); + virtual void SetDescription(const CVariant& label); + + virtual void OnResetSettings(); + + /*! + \brief A setting control has been interacted with by the user + + This method is called when the user manually interacts (clicks, + edits) with a setting control. It contains handling for both + delayed and undelayed settings and either starts the delay timer + or triggers the setting change which, on success, results in a + callback to OnSettingChanged(). + + \param pSettingControl Setting control that has been interacted with + */ + virtual void OnClick(const BaseSettingControlPtr& pSettingControl); + + void UpdateSettingControl(const std::string& settingId, bool updateDisplayOnly = false); + void UpdateSettingControl(const BaseSettingControlPtr& pSettingControl, + bool updateDisplayOnly = false); + void SetControlLabel(int controlId, const CVariant& label); + + BaseSettingControlPtr GetSettingControl(const std::string& setting); + BaseSettingControlPtr GetSettingControl(int controlId); + + CGUIControl* AddSeparator(float width, int& iControlID); + CGUIControl* AddGroupLabel(const std::shared_ptr<CSettingGroup>& group, + float width, + int& iControlID); + + std::vector<std::shared_ptr<CSettingCategory>> m_categories; + std::vector<BaseSettingControlPtr> m_settingControls; + + int m_iSetting; + int m_iCategory; + std::shared_ptr<CSettingAction> m_resetSetting; + std::shared_ptr<CSettingCategory> m_dummyCategory; + + CGUISpinControlEx* m_pOriginalSpin; + CGUISettingsSliderControl* m_pOriginalSlider; + CGUIRadioButtonControl* m_pOriginalRadioButton; + CGUIColorButtonControl* m_pOriginalColorButton; + CGUIButtonControl* m_pOriginalCategoryButton; + CGUIButtonControl* m_pOriginalButton; + CGUIEditControl* m_pOriginalEdit; + CGUIImage* m_pOriginalImage; + CGUILabelControl* m_pOriginalGroupTitle; + bool m_newOriginalEdit; + + BaseSettingControlPtr + m_delayedSetting; ///< Current delayed setting \sa CBaseSettingControl::SetDelayed() + CTimer m_delayedTimer; ///< Delayed setting timer + + bool m_confirmed; + int m_focusedControl, m_fadedControl; +}; diff --git a/xbmc/settings/dialogs/GUIDialogSettingsManagerBase.cpp b/xbmc/settings/dialogs/GUIDialogSettingsManagerBase.cpp new file mode 100644 index 0000000..e01edfb --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogSettingsManagerBase.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSettingsManagerBase.h" + +#include "settings/lib/SettingsManager.h" + +#include <cassert> + +CGUIDialogSettingsManagerBase::CGUIDialogSettingsManagerBase(int windowId, const std::string &xmlFile) + : CGUIDialogSettingsBase(windowId, xmlFile) +{ } + +CGUIDialogSettingsManagerBase::~CGUIDialogSettingsManagerBase() = default; + +std::shared_ptr<CSetting> CGUIDialogSettingsManagerBase::GetSetting(const std::string &settingId) +{ + assert(GetSettingsManager() != nullptr); + + return GetSettingsManager()->GetSetting(settingId); +} + +bool CGUIDialogSettingsManagerBase::OnOkay() +{ + if (Save()) + { + CGUIDialogSettingsBase::OnOkay(); + return true; + } + + return false; +} + +std::set<std::string> CGUIDialogSettingsManagerBase::CreateSettings() +{ + assert(GetSettingsManager() != nullptr); + + std::set<std::string> settings = CGUIDialogSettingsBase::CreateSettings(); + + if (!settings.empty()) + GetSettingsManager()->RegisterCallback(this, settings); + + return settings; +} + +void CGUIDialogSettingsManagerBase::FreeSettingsControls() +{ + CGUIDialogSettingsBase::FreeSettingsControls(); + + if (GetSettingsManager() != nullptr) + GetSettingsManager()->UnregisterCallback(this); +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManagerBase::CreateControl(const std::string &controlType) const +{ + assert(GetSettingsManager() != nullptr); + + return GetSettingsManager()->CreateControl(controlType); +} diff --git a/xbmc/settings/dialogs/GUIDialogSettingsManagerBase.h b/xbmc/settings/dialogs/GUIDialogSettingsManagerBase.h new file mode 100644 index 0000000..42c3c31 --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogSettingsManagerBase.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/dialogs/GUIDialogSettingsBase.h" + +class CSettingsManager; + +class CGUIDialogSettingsManagerBase : public CGUIDialogSettingsBase +{ +public: + CGUIDialogSettingsManagerBase(int windowId, const std::string &xmlFile); + ~CGUIDialogSettingsManagerBase() override; + +protected: + virtual bool Save() = 0; + virtual CSettingsManager* GetSettingsManager() const = 0; + + // implementation of CGUIDialogSettingsBase + std::shared_ptr<CSetting> GetSetting(const std::string &settingId) override; + bool OnOkay() override; + + std::set<std::string> CreateSettings() override; + void FreeSettingsControls() override; + + // implementation of ISettingControlCreator + std::shared_ptr<ISettingControl> CreateControl(const std::string &controlType) const override; +}; diff --git a/xbmc/settings/dialogs/GUIDialogSettingsManualBase.cpp b/xbmc/settings/dialogs/GUIDialogSettingsManualBase.cpp new file mode 100644 index 0000000..84114db --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogSettingsManualBase.cpp @@ -0,0 +1,1617 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIDialogSettingsManualBase.h" + +#include "settings/SettingAddon.h" +#include "settings/SettingDateTime.h" +#include "settings/SettingPath.h" +#include "settings/SettingUtils.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingSection.h" +#include "settings/lib/SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <string> +#include <vector> + +CGUIDialogSettingsManualBase::CGUIDialogSettingsManualBase(int windowId, const std::string &xmlFile) + : CGUIDialogSettingsManagerBase(windowId, xmlFile) + , m_settingsManager(nullptr) +{ } + +CGUIDialogSettingsManualBase::~CGUIDialogSettingsManualBase() +{ + if (GetSettingsManager() != nullptr) + { + GetSettingsManager()->Clear(); + m_section = nullptr; + delete GetSettingsManager(); + } +} + +void CGUIDialogSettingsManualBase::SetupView() +{ + InitializeSettings(); + + if (GetSettingsManager() != nullptr) + { + // add the created setting section to the settings manager and mark it as ready + GetSettingsManager()->AddSection(m_section); + GetSettingsManager()->SetInitialized(); + GetSettingsManager()->SetLoaded(); + } + + CGUIDialogSettingsBase::SetupView(); +} + +CSettingsManager* CGUIDialogSettingsManualBase::GetSettingsManager() const +{ + if (m_settingsManager == nullptr) + m_settingsManager = new CSettingsManager(); + + return m_settingsManager; +} + +void CGUIDialogSettingsManualBase::InitializeSettings() +{ + if (GetSettingsManager() != nullptr) + { + GetSettingsManager()->Clear(); + m_section = NULL; + + // create a std::make_shared<section + m_section = std::make_shared<CSettingSection>(GetProperty("xmlfile").asString(), GetSettingsManager()); + } +} + +SettingCategoryPtr CGUIDialogSettingsManualBase::AddCategory(const std::string &id, int label, int help /* = -1 */) +{ + if (id.empty()) + return NULL; + + SettingCategoryPtr category = std::make_shared<CSettingCategory>(id, GetSettingsManager()); + if (category == NULL) + return NULL; + + category->SetLabel(label); + if (help >= 0) + category->SetHelp(help); + + m_section->AddCategory(category); + return category; +} + +SettingGroupPtr CGUIDialogSettingsManualBase::AddGroup(const SettingCategoryPtr& category, + int label /* = -1 */, + int help /* = -1 */, + bool separatorBelowLabel /* = true */, + bool hideSeparator /* = false */) +{ + if (category == NULL) + return NULL; + + size_t groups = category->GetGroups().size(); + + SettingGroupPtr group = std::make_shared<CSettingGroup>(StringUtils::Format("{0}", groups + 1), GetSettingsManager()); + if (group == NULL) + return NULL; + + if (label >= 0) + group->SetLabel(label); + if (help >= 0) + group->SetHelp(help); + group->SetControl(GetTitleControl(separatorBelowLabel, hideSeparator)); + + category->AddGroup(group); + return group; +} + +std::shared_ptr<CSettingBool> CGUIDialogSettingsManualBase::AddToggle(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + bool value, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingBool> setting = std::make_shared<CSettingBool>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetCheckmarkControl(delayed)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddEdit( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int minimum /* = 0 */, + int step /* = 1 */, + int maximum /* = 0 */, + bool verifyNewValue /* = false */, + int heading /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, minimum, step, maximum, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetEditControl("integer", delayed, false, verifyNewValue, heading)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingNumber> CGUIDialogSettingsManualBase::AddEdit( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float value, + float minimum /* = 0.0f */, + float step /* = 1.0f */, + float maximum /* = 0.0f */, + bool verifyNewValue /* = false */, + int heading /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingNumber> setting = std::make_shared<CSettingNumber>(id, label, value, minimum, step, maximum, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetEditControl("number", delayed, false, verifyNewValue, heading)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingString> CGUIDialogSettingsManualBase::AddEdit(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty /* = false */, + bool hidden /* = false */, + int heading /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingString> setting = std::make_shared<CSettingString>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetEditControl("string", delayed, hidden, false, heading)); + setting->SetAllowEmpty(allowEmpty); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingString> CGUIDialogSettingsManualBase::AddIp(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty /* = false */, + int heading /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingString> setting = std::make_shared<CSettingString>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetEditControl("ip", delayed, false, false, heading)); + setting->SetAllowEmpty(allowEmpty); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingString> CGUIDialogSettingsManualBase::AddPasswordMd5( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty /* = false */, + int heading /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingString> setting = std::make_shared<CSettingString>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetEditControl("md5", delayed, false, false, heading)); + setting->SetAllowEmpty(allowEmpty); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingAction> CGUIDialogSettingsManualBase::AddButton( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& data /* = "" */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingAction> setting = std::make_shared<CSettingAction>(id, label, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetButtonControl("action", delayed)); + setting->SetData(data); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingString> CGUIDialogSettingsManualBase::AddInfoLabelButton( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& info, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingString> setting = std::make_shared<CSettingString>(id, label, info, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetButtonControl("infolabel", false)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingAddon> CGUIDialogSettingsManualBase::AddAddon( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + ADDON::AddonType addonType, + bool allowEmpty /* = false */, + int heading /* = -1 */, + bool hideValue /* = false */, + bool showInstalledAddons /* = true */, + bool showInstallableAddons /* = false */, + bool showMoreAddons /* = true */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingAddon> setting = std::make_shared<CSettingAddon>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetButtonControl("addon", delayed, heading, hideValue, showInstalledAddons, showInstallableAddons, showMoreAddons)); + setting->SetAddonType(addonType); + setting->SetAllowEmpty(allowEmpty); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingPath> CGUIDialogSettingsManualBase::AddPath( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool writable /* = true */, + const std::vector<std::string>& sources /* = std::vector<std::string>() */, + bool allowEmpty /* = false */, + int heading /* = -1 */, + bool hideValue /* = false */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingPath> setting = std::make_shared<CSettingPath>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetButtonControl("path", delayed, heading, hideValue)); + setting->SetWritable(writable); + setting->SetSources(sources); + setting->SetAllowEmpty(allowEmpty); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingDate> CGUIDialogSettingsManualBase::AddDate(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty /* = false */, + int heading /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingDate> setting = std::make_shared<CSettingDate>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetButtonControl("date", delayed, heading)); + setting->SetAllowEmpty(allowEmpty); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingTime> CGUIDialogSettingsManualBase::AddTime(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty /* = false */, + int heading /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingTime> setting = std::make_shared<CSettingTime>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetButtonControl("time", delayed, heading)); + setting->SetAllowEmpty(allowEmpty); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingString> CGUIDialogSettingsManualBase::AddSpinner( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + StringSettingOptionsFiller filler, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || filler == NULL || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingString> setting = std::make_shared<CSettingString>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("string", delayed)); + setting->SetOptionsFiller(filler, this); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddSpinner(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int minimum, + int step, + int maximum, + int formatLabel /* = -1 */, + int minimumLabel /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("string", delayed, minimumLabel, formatLabel)); + setting->SetMinimum(minimum); + setting->SetStep(step); + setting->SetMaximum(maximum); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddSpinner( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int minimum, + int step, + int maximum, + const std::string& formatString, + int minimumLabel /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("string", delayed, minimumLabel, -1, formatString)); + setting->SetMinimum(minimum); + setting->SetStep(step); + setting->SetMaximum(maximum); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddSpinner( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const TranslatableIntegerSettingOptions& entries, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || entries.empty() || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("string", delayed)); + setting->SetTranslatableOptions(entries); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddSpinner( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const IntegerSettingOptions& entries, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || entries.empty() || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("string", delayed)); + setting->SetOptions(entries); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddSpinner( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + IntegerSettingOptionsFiller filler, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || filler == NULL || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("string", delayed)); + setting->SetOptionsFiller(filler, this); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingNumber> CGUIDialogSettingsManualBase::AddSpinner( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float value, + float minimum, + float step, + float maximum, + int formatLabel /* = -1 */, + int minimumLabel /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingNumber> setting = std::make_shared<CSettingNumber>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("number", delayed, minimumLabel, formatLabel)); + setting->SetMinimum(static_cast<double>(minimum)); + setting->SetStep(static_cast<double>(step)); + setting->SetMaximum(static_cast<double>(maximum)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingNumber> CGUIDialogSettingsManualBase::AddSpinner( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float value, + float minimum, + float step, + float maximum, + const std::string& formatString, + int minimumLabel /* = -1 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingNumber> setting = std::make_shared<CSettingNumber>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSpinnerControl("number", delayed, minimumLabel, -1, formatString)); + setting->SetMinimum(static_cast<double>(minimum)); + setting->SetStep(static_cast<double>(step)); + setting->SetMaximum(static_cast<double>(maximum)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingString> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + StringSettingOptionsFiller filler, + int heading, + bool visible /* = true */, + int help /* = -1 */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || filler == NULL || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingString> setting = std::make_shared<CSettingString>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetListControl("string", false, heading, false, nullptr, details)); + setting->SetOptionsFiller(filler, this); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const TranslatableIntegerSettingOptions& entries, + int heading, + bool visible /* = true */, + int help /* = -1 */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || entries.empty() || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetListControl("integer", false, heading, false, nullptr, details)); + setting->SetTranslatableOptions(entries); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const IntegerSettingOptions& entries, + int heading, + bool visible /* = true */, + int help /* = -1 */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || entries.empty() || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetListControl("integer", false, heading, false, nullptr, details)); + setting->SetOptions(entries); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + IntegerSettingOptionsFiller filler, + int heading, + bool visible /* = true */, + int help /* = -1 */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || filler == NULL || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetListControl("integer", false, heading, false, nullptr, details)); + setting->SetOptionsFiller(filler, this); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<std::string> values, + StringSettingOptionsFiller filler, + int heading, + int minimumItems /* = 0 */, + int maximumItems /* = -1 */, + bool visible /* = true */, + int help /* = -1 */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || filler == NULL || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingString> settingDefinition = std::make_shared<CSettingString>(id, GetSettingsManager()); + if (settingDefinition == NULL) + return NULL; + + settingDefinition->SetOptionsFiller(filler, this); + + std::shared_ptr<CSettingList> setting = std::make_shared<CSettingList>(id, settingDefinition, label, GetSettingsManager()); + if (setting == NULL) + return NULL; + + std::vector<CVariant> valueList; + for (std::vector<std::string>::const_iterator itValue = values.begin(); itValue != values.end(); ++itValue) + valueList.emplace_back(*itValue); + SettingList settingValues; + if (!CSettingUtils::ValuesToList(setting, valueList, settingValues)) + return NULL; + // setting the default will also set the actual value on an unchanged setting + setting->SetDefault(settingValues); + + setting->SetControl(GetListControl("string", false, heading, true, nullptr, details)); + setting->SetMinimumItems(minimumItems); + setting->SetMaximumItems(maximumItems); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<int> values, + const TranslatableIntegerSettingOptions& entries, + int heading, + int minimumItems /* = 0 */, + int maximumItems /* = -1 */, + bool visible /* = true */, + int help /* = -1 */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || entries.empty() || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> settingDefinition = std::make_shared<CSettingInt>(id, GetSettingsManager()); + if (settingDefinition == NULL) + return NULL; + + settingDefinition->SetTranslatableOptions(entries); + + std::shared_ptr<CSettingList> setting = std::make_shared<CSettingList>(id, settingDefinition, label, GetSettingsManager()); + if (setting == NULL) + return NULL; + + std::vector<CVariant> valueList; + for (std::vector<int>::const_iterator itValue = values.begin(); itValue != values.end(); ++itValue) + valueList.emplace_back(*itValue); + SettingList settingValues; + if (!CSettingUtils::ValuesToList(setting, valueList, settingValues)) + return NULL; + // setting the default will also set the actual value on an unchanged setting + setting->SetDefault(settingValues); + + setting->SetControl(GetListControl("integer", false, heading, true, nullptr, details)); + setting->SetMinimumItems(minimumItems); + setting->SetMaximumItems(maximumItems); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<int> values, + const IntegerSettingOptions& entries, + int heading, + int minimumItems /* = 0 */, + int maximumItems /* = -1 */, + bool visible /* = true */, + int help /* = -1 */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || entries.empty() || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> settingDefinition = std::make_shared<CSettingInt>(id, GetSettingsManager()); + if (settingDefinition == NULL) + return NULL; + + settingDefinition->SetOptions(entries); + + std::shared_ptr<CSettingList> setting = std::make_shared<CSettingList>(id, settingDefinition, label, GetSettingsManager()); + if (setting == NULL) + return NULL; + + std::vector<CVariant> valueList; + for (std::vector<int>::const_iterator itValue = values.begin(); itValue != values.end(); ++itValue) + valueList.emplace_back(*itValue); + SettingList settingValues; + if (!CSettingUtils::ValuesToList(setting, valueList, settingValues)) + return NULL; + // setting the default will also set the actual value on an unchanged setting + setting->SetDefault(settingValues); + + setting->SetControl(GetListControl("integer", false, heading, true, nullptr, details)); + setting->SetMinimumItems(minimumItems); + setting->SetMaximumItems(maximumItems); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddList( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<int> values, + IntegerSettingOptionsFiller filler, + int heading, + int minimumItems /* = 0 */, + int maximumItems /* = -1 */, + bool visible /* = true */, + int help /* = -1 */, + SettingControlListValueFormatter formatter /* = NULL */, + bool details /* = false */) +{ + if (group == NULL || id.empty() || label < 0 || filler == NULL || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> settingDefinition = std::make_shared<CSettingInt>(id, GetSettingsManager()); + if (settingDefinition == NULL) + return NULL; + + settingDefinition->SetOptionsFiller(filler, this); + + std::shared_ptr<CSettingList> setting = std::make_shared<CSettingList>(id, settingDefinition, label, GetSettingsManager()); + if (setting == NULL) + return NULL; + + std::vector<CVariant> valueList; + for (std::vector<int>::const_iterator itValue = values.begin(); itValue != values.end(); ++itValue) + valueList.emplace_back(*itValue); + SettingList settingValues; + if (!CSettingUtils::ValuesToList(setting, valueList, settingValues)) + return NULL; + // setting the default will also set the actual value on an unchanged setting + setting->SetDefault(settingValues); + + setting->SetControl(GetListControl("integer", false, heading, true, formatter, details)); + setting->SetMinimumItems(minimumItems); + setting->SetMaximumItems(maximumItems); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddPercentageSlider( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int formatLabel, + int step /* = 1 */, + int heading /* = -1 */, + bool usePopup /* = false */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSliderControl("percentage", delayed, heading, usePopup, formatLabel)); + setting->SetMinimum(0); + setting->SetStep(step); + setting->SetMaximum(100); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddPercentageSlider( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const std::string& formatString, + int step /* = 1 */, + int heading /* = -1 */, + bool usePopup /* = false */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSliderControl("percentage", delayed, heading, usePopup, -1, formatString)); + setting->SetMinimum(0); + setting->SetStep(step); + setting->SetMaximum(100); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddSlider(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int formatLabel, + int minimum, + int step, + int maximum, + int heading /* = -1 */, + bool usePopup /* = false */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSliderControl("integer", delayed, heading, usePopup, formatLabel)); + setting->SetMinimum(minimum); + setting->SetStep(step); + setting->SetMaximum(maximum); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingInt> CGUIDialogSettingsManualBase::AddSlider( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const std::string& formatString, + int minimum, + int step, + int maximum, + int heading /* = -1 */, + bool usePopup /* = false */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> setting = std::make_shared<CSettingInt>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSliderControl("integer", delayed, heading, usePopup, -1, formatString)); + setting->SetMinimum(minimum); + setting->SetStep(step); + setting->SetMaximum(maximum); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingNumber> CGUIDialogSettingsManualBase::AddSlider( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float value, + int formatLabel, + float minimum, + float step, + float maximum, + int heading /* = -1 */, + bool usePopup /* = false */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingNumber> setting = std::make_shared<CSettingNumber>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSliderControl("number", delayed, heading, usePopup, formatLabel)); + setting->SetMinimum(static_cast<double>(minimum)); + setting->SetStep(static_cast<double>(step)); + setting->SetMaximum(static_cast<double>(maximum)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingNumber> CGUIDialogSettingsManualBase::AddSlider( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float value, + const std::string& formatString, + float minimum, + float step, + float maximum, + int heading /* = -1 */, + bool usePopup /* = false */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingNumber> setting = std::make_shared<CSettingNumber>(id, label, value, GetSettingsManager()); + if (setting == NULL) + return NULL; + + setting->SetControl(GetSliderControl("number", delayed, heading, usePopup, -1, formatString)); + setting->SetMinimum(static_cast<double>(minimum)); + setting->SetStep(static_cast<double>(step)); + setting->SetMaximum(static_cast<double>(maximum)); + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddPercentageRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int valueFormatLabel, + int step /* = 1 */, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, 0, step, 100, "percentage", formatLabel, valueFormatLabel, "", delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddPercentageRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + const std::string& valueFormatString /* = "{:d} %" */, + int step /* = 1 */, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, 0, step, 100, "percentage", formatLabel, -1, valueFormatString, delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddRange(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + int valueFormatLabel, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "integer", formatLabel, valueFormatLabel, "", delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& valueFormatString /* = "{:d}" */, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "integer", formatLabel, -1, valueFormatString, delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddRange(const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float valueLower, + float valueUpper, + float minimum, + float step, + float maximum, + int valueFormatLabel, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "number", formatLabel, valueFormatLabel, "", delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float valueLower, + float valueUpper, + float minimum, + float step, + float maximum, + const std::string& valueFormatString /* = "{:.1f}" */, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "number", formatLabel, -1, valueFormatString, delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddDateRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + int valueFormatLabel, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "date", formatLabel, valueFormatLabel, "", delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddDateRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& valueFormatString /* = "" */, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "date", formatLabel, -1, valueFormatString, delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddTimeRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + int valueFormatLabel, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "time", formatLabel, valueFormatLabel, "", delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddTimeRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& valueFormatString /* = "mm:ss" */, + int formatLabel /* = 21469 */, + bool delayed /* = false */, + bool visible /* = true */, + int help /* = -1 */) +{ + return AddRange(group, id, label, level, valueLower, valueUpper, minimum, step, maximum, "time", formatLabel, -1, valueFormatString, delayed, visible, help); +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& format, + int formatLabel, + int valueFormatLabel, + const std::string& valueFormatString, + bool delayed, + bool visible, + int help) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingInt> settingDefinition = std::make_shared<CSettingInt>(id, GetSettingsManager()); + if (settingDefinition == NULL) + return NULL; + + settingDefinition->SetMinimum(minimum); + settingDefinition->SetStep(step); + settingDefinition->SetMaximum(maximum); + + std::shared_ptr<CSettingList> setting = std::make_shared<CSettingList>(id, settingDefinition, label, GetSettingsManager()); + if (setting == NULL) + return NULL; + + std::vector<CVariant> valueList; + valueList.emplace_back(valueLower); + valueList.emplace_back(valueUpper); + SettingList settingValues; + if (!CSettingUtils::ValuesToList(setting, valueList, settingValues)) + return NULL; + // setting the default will also set the actual value on an unchanged setting + setting->SetDefault(settingValues); + + setting->SetControl(GetRangeControl(format, delayed, formatLabel, valueFormatLabel, valueFormatString)); + setting->SetMinimumItems(2); + setting->SetMaximumItems(2); + + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +std::shared_ptr<CSettingList> CGUIDialogSettingsManualBase::AddRange( + const SettingGroupPtr& group, + const std::string& id, + int label, + SettingLevel level, + float valueLower, + float valueUpper, + float minimum, + float step, + float maximum, + const std::string& format, + int formatLabel, + int valueFormatLabel, + const std::string& valueFormatString, + bool delayed, + bool visible, + int help) +{ + if (group == NULL || id.empty() || label < 0 || + GetSetting(id) != NULL) + return NULL; + + std::shared_ptr<CSettingNumber> settingDefinition = std::make_shared<CSettingNumber>(id, GetSettingsManager()); + if (settingDefinition == NULL) + return NULL; + + settingDefinition->SetMinimum(static_cast<double>(minimum)); + settingDefinition->SetStep(static_cast<double>(step)); + settingDefinition->SetMaximum(static_cast<double>(maximum)); + + std::shared_ptr<CSettingList> setting = std::make_shared<CSettingList>(id, settingDefinition, label, GetSettingsManager()); + if (setting == NULL) + return NULL; + + std::vector<CVariant> valueList; + valueList.emplace_back(valueLower); + valueList.emplace_back(valueUpper); + SettingList settingValues; + if (!CSettingUtils::ValuesToList(setting, valueList, settingValues)) + return NULL; + // setting the default will also set the actual value on an unchanged setting + setting->SetDefault(settingValues); + + setting->SetControl(GetRangeControl(format, delayed, formatLabel, valueFormatLabel, valueFormatString)); + setting->SetMinimumItems(2); + setting->SetMaximumItems(2); + + setSettingDetails(setting, level, visible, help); + + group->AddSetting(setting); + return setting; +} + +void CGUIDialogSettingsManualBase::setSettingDetails(const std::shared_ptr<CSetting>& setting, + SettingLevel level, + bool visible, + int help) +{ + if (setting == NULL) + return; + + if (level < SettingLevel::Basic) + level = SettingLevel::Basic; + else if (level > SettingLevel::Expert) + level = SettingLevel::Expert; + + setting->SetLevel(level); + setting->SetVisible(visible); + if (help >= 0) + setting->SetHelp(help); +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetCheckmarkControl(bool delayed /* = false */) +{ + std::shared_ptr<CSettingControlCheckmark> control = std::make_shared<CSettingControlCheckmark>(); + control->SetDelayed(delayed); + + return control; +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetTitleControl(bool separatorBelowLabel /* = true */, bool hideSeparator /* = false */) +{ + std::shared_ptr<CSettingControlTitle> control = std::make_shared<CSettingControlTitle>(); + control->SetSeparatorBelowLabel(separatorBelowLabel); + control->SetSeparatorHidden(hideSeparator); + + return control; +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetEditControl(const std::string &format, bool delayed /* = false */, bool hidden /* = false */, bool verifyNewValue /* = false */, int heading /* = -1 */) +{ + std::shared_ptr<CSettingControlEdit> control = std::make_shared<CSettingControlEdit>(); + if (!control->SetFormat(format)) + return NULL; + + control->SetDelayed(delayed); + control->SetHidden(hidden); + control->SetVerifyNewValue(verifyNewValue); + control->SetHeading(heading); + + return control; +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetButtonControl(const std::string &format, bool delayed /* = false */, int heading /* = -1 */, bool hideValue /* = false */, + bool showInstalledAddons /* = true */, bool showInstallableAddons /* = false */, bool showMoreAddons /* = true */) +{ + std::shared_ptr<CSettingControlButton> control = std::make_shared<CSettingControlButton>(); + if (!control->SetFormat(format)) + return NULL; + + control->SetDelayed(delayed); + control->SetHeading(heading); + control->SetHideValue(hideValue); + control->SetShowInstalledAddons(showInstalledAddons); + control->SetShowInstallableAddons(showInstallableAddons); + control->SetShowMoreAddons(showMoreAddons); + + return control; +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetSpinnerControl(const std::string &format, bool delayed /* = false */, int minimumLabel /* = -1 */, int formatLabel /* = -1 */, const std::string &formatString /* = "" */) +{ + std::shared_ptr<CSettingControlSpinner> control = std::make_shared<CSettingControlSpinner>(); + if (!control->SetFormat(format)) + return NULL; + + control->SetDelayed(delayed); + if (formatLabel >= 0) + control->SetFormatLabel(formatLabel); + if (!formatString.empty()) + control->SetFormatString(formatString); + if (minimumLabel >= 0) + control->SetMinimumLabel(minimumLabel); + + return control; +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetListControl( + const std::string& format, + bool delayed /* = false */, + int heading /* = -1 */, + bool multiselect /* = false */, + SettingControlListValueFormatter formatter /* = NULL */, + bool details /* = false */) +{ + std::shared_ptr<CSettingControlList> control = std::make_shared<CSettingControlList>(); + if (!control->SetFormat(format)) + return NULL; + + control->SetDelayed(delayed); + control->SetHeading(heading); + control->SetMultiSelect(multiselect); + control->SetFormatter(formatter); + control->SetUseDetails(details); + + return control; +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetSliderControl(const std::string &format, bool delayed /* = false */, int heading /* = -1 */, bool usePopup /* = false */, + int formatLabel /* = -1 */, const std::string &formatString /* = "" */) +{ + std::shared_ptr<CSettingControlSlider> control = std::make_shared<CSettingControlSlider>(); + if (!control->SetFormat(format)) + return NULL; + + control->SetDelayed(delayed); + if (heading >= 0) + control->SetHeading(heading); + control->SetPopup(usePopup); + if (formatLabel >= 0) + control->SetFormatLabel(formatLabel); + if (!formatString.empty()) + control->SetFormatString(formatString); + + return control; +} + +std::shared_ptr<ISettingControl> CGUIDialogSettingsManualBase::GetRangeControl(const std::string &format, bool delayed /* = false */, int formatLabel /* = -1 */, + int valueFormatLabel /* = -1 */, const std::string &valueFormatString /* = "" */) +{ + std::shared_ptr<CSettingControlRange> control = std::make_shared<CSettingControlRange>(); + if (!control->SetFormat(format)) + return NULL; + + control->SetDelayed(delayed); + if (formatLabel >= 0) + control->SetFormatLabel(formatLabel); + if (valueFormatLabel >= 0) + control->SetValueFormatLabel(valueFormatLabel); + if (!valueFormatString.empty()) + control->SetValueFormat(valueFormatString); + + return control; +} diff --git a/xbmc/settings/dialogs/GUIDialogSettingsManualBase.h b/xbmc/settings/dialogs/GUIDialogSettingsManualBase.h new file mode 100644 index 0000000..338259c --- /dev/null +++ b/xbmc/settings/dialogs/GUIDialogSettingsManualBase.h @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "addons/IAddon.h" +#include "settings/dialogs/GUIDialogSettingsManagerBase.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingLevel.h" + +#include <map> +#include <memory> +#include <string> +#include <vector> + +class CSetting; +class CSettingAction; +class CSettingAddon; +class CSettingBool; +class CSettingCategory; +class CSettingDate; +class CSettingGroup; +class CSettingInt; +class CSettingList; +class CSettingNumber; +class CSettingPath; +class CSettingSection; +class CSettingString; +class CSettingsManager; +class CSettingTime; + +class CGUIDialogSettingsManualBase : public CGUIDialogSettingsManagerBase +{ +public: + CGUIDialogSettingsManualBase(int windowId, const std::string &xmlFile); + ~CGUIDialogSettingsManualBase() override; + +protected: + // implementation of CGUIDialogSettingsBase + std::shared_ptr<CSettingSection> GetSection() override { return m_section; } + void SetupView() override; + + // implementation of CGUIDialogSettingsManagerBase + CSettingsManager* GetSettingsManager() const override; + + virtual void InitializeSettings(); + + std::shared_ptr<CSettingCategory> AddCategory(const std::string &id, int label, int help = -1); + std::shared_ptr<CSettingGroup> AddGroup(const std::shared_ptr<CSettingCategory>& category, + int label = -1, + int help = -1, + bool separatorBelowLabel = true, + bool hideSeparator = false); + // checkmark control + std::shared_ptr<CSettingBool> AddToggle(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + bool value, + bool delayed = false, + bool visible = true, + int help = -1); + // edit controls + std::shared_ptr<CSettingInt> AddEdit(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int minimum = 0, + int step = 1, + int maximum = 0, + bool verifyNewValue = false, + int heading = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingNumber> AddEdit(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float value, + float minimum = 0.0f, + float step = 1.0f, + float maximum = 0.0f, + bool verifyNewValue = false, + int heading = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingString> AddEdit(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty = false, + bool hidden = false, + int heading = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingString> AddIp(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty = false, + int heading = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingString> AddPasswordMd5(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty = false, + int heading = -1, + bool delayed = false, + bool visible = true, + int help = -1); + // button controls + std::shared_ptr<CSettingAction> AddButton(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& data = "", + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingString> AddInfoLabelButton(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& info, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingAddon> AddAddon(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + ADDON::AddonType addonType, + bool allowEmpty = false, + int heading = -1, + bool hideValue = false, + bool showInstalledAddons = true, + bool showInstallableAddons = false, + bool showMoreAddons = true, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingPath> AddPath( + const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool writable = true, + const std::vector<std::string>& sources = std::vector<std::string>(), + bool allowEmpty = false, + int heading = -1, + bool hideValue = false, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingDate> AddDate(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty = false, + int heading = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingTime> AddTime(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + bool allowEmpty = false, + int heading = -1, + bool delayed = false, + bool visible = true, + int help = -1); + + // spinner controls + std::shared_ptr<CSettingString> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + StringSettingOptionsFiller filler, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int minimum, + int step, + int maximum, + int formatLabel = -1, + int minimumLabel = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int minimum, + int step, + int maximum, + const std::string& formatString, + int minimumLabel = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const TranslatableIntegerSettingOptions& entries, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const IntegerSettingOptions& entries, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + IntegerSettingOptionsFiller filler, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingNumber> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float value, + float minimum, + float step, + float maximum, + int formatLabel = -1, + int minimumLabel = -1, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingNumber> AddSpinner(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float value, + float minimum, + float step, + float maximum, + const std::string& formatString, + int minimumLabel = -1, + bool delayed = false, + bool visible = true, + int help = -1); + + // list controls + std::shared_ptr<CSettingString> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + const std::string& value, + StringSettingOptionsFiller filler, + int heading, + bool visible = true, + int help = -1, + bool details = false); + std::shared_ptr<CSettingInt> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const TranslatableIntegerSettingOptions& entries, + int heading, + bool visible = true, + int help = -1, + bool details = false); + std::shared_ptr<CSettingInt> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const IntegerSettingOptions& entries, + int heading, + bool visible = true, + int help = -1, + bool details = false); + std::shared_ptr<CSettingInt> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + IntegerSettingOptionsFiller filler, + int heading, + bool visible = true, + int help = -1, + bool details = false); + std::shared_ptr<CSettingList> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<std::string> values, + StringSettingOptionsFiller filler, + int heading, + int minimumItems = 0, + int maximumItems = -1, + bool visible = true, + int help = -1, + bool details = false); + std::shared_ptr<CSettingList> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<int> values, + const TranslatableIntegerSettingOptions& entries, + int heading, + int minimumItems = 0, + int maximumItems = -1, + bool visible = true, + int help = -1, + bool details = false); + std::shared_ptr<CSettingList> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<int> values, + const IntegerSettingOptions& entries, + int heading, + int minimumItems = 0, + int maximumItems = -1, + bool visible = true, + int help = -1, + bool details = false); + std::shared_ptr<CSettingList> AddList(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + std::vector<int> values, + IntegerSettingOptionsFiller filler, + int heading, + int minimumItems = 0, + int maximumItems = -1, + bool visible = true, + int help = -1, + SettingControlListValueFormatter formatter = nullptr, + bool details = false); + + // slider controls + std::shared_ptr<CSettingInt> AddPercentageSlider(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int formatLabel, + int step = 1, + int heading = -1, + bool usePopup = false, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddPercentageSlider(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const std::string& formatString, + int step = 1, + int heading = -1, + bool usePopup = false, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddSlider(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + int formatLabel, + int minimum, + int step, + int maximum, + int heading = -1, + bool usePopup = false, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingInt> AddSlider(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int value, + const std::string& formatString, + int minimum, + int step, + int maximum, + int heading = -1, + bool usePopup = false, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingNumber> AddSlider(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float value, + int formatLabel, + float minimum, + float step, + float maximum, + int heading = -1, + bool usePopup = false, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingNumber> AddSlider(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float value, + const std::string& formatString, + float minimum, + float step, + float maximum, + int heading = -1, + bool usePopup = false, + bool delayed = false, + bool visible = true, + int help = -1); + + // range controls + std::shared_ptr<CSettingList> AddPercentageRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int valueFormatLabel, + int step = 1, + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddPercentageRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + const std::string& valueFormatString = "{:d} %", + int step = 1, + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + int valueFormatLabel, + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& valueFormatString = "{:d}", + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float valueLower, + float valueUpper, + float minimum, + float step, + float maximum, + int valueFormatLabel, + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float valueLower, + float valueUpper, + float minimum, + float step, + float maximum, + const std::string& valueFormatString = "{:.1f}", + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddDateRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + int valueFormatLabel, + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddDateRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& valueFormatString = "", + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddTimeRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + int valueFormatLabel, + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + std::shared_ptr<CSettingList> AddTimeRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& valueFormatString = "mm:ss", + int formatLabel = 21469, + bool delayed = false, + bool visible = true, + int help = -1); + + std::shared_ptr<ISettingControl> GetTitleControl(bool separatorBelowLabel = true, bool hideSeparator = false); + std::shared_ptr<ISettingControl> GetCheckmarkControl(bool delayed = false); + std::shared_ptr<ISettingControl> GetEditControl(const std::string &format, bool delayed = false, bool hidden = false, bool verifyNewValue = false, int heading = -1); + std::shared_ptr<ISettingControl> GetButtonControl(const std::string &format, bool delayed = false, int heading = -1, bool hideValue = false, bool showInstalledAddons = true, + bool showInstallableAddons = false, bool showMoreAddons = true); + std::shared_ptr<ISettingControl> GetSpinnerControl(const std::string &format, bool delayed = false, int minimumLabel = -1, int formatLabel = -1, const std::string &formatString = ""); + std::shared_ptr<ISettingControl> GetListControl( + const std::string& format, + bool delayed = false, + int heading = -1, + bool multiselect = false, + SettingControlListValueFormatter formatter = nullptr, + bool details = false); + std::shared_ptr<ISettingControl> GetSliderControl(const std::string &format, bool delayed = false, int heading = -1, bool usePopup = false, int formatLabel = -1, const std::string &formatString = ""); + std::shared_ptr<ISettingControl> GetRangeControl(const std::string &format, bool delayed = false, int formatLabel = -1, int valueFormatLabel = -1, const std::string &valueFormatString = ""); + +private: + std::shared_ptr<CSettingList> AddRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + int valueLower, + int valueUpper, + int minimum, + int step, + int maximum, + const std::string& format, + int formatLabel, + int valueFormatLabel, + const std::string& valueFormatString, + bool delayed, + bool visible, + int help); + std::shared_ptr<CSettingList> AddRange(const std::shared_ptr<CSettingGroup>& group, + const std::string& id, + int label, + SettingLevel level, + float valueLower, + float valueUpper, + float minimum, + float step, + float maximum, + const std::string& format, + int formatLabel, + int valueFormatLabel, + const std::string& valueFormatString, + bool delayed, + bool visible, + int help); + + void setSettingDetails(const std::shared_ptr<CSetting>& setting, + SettingLevel level, + bool visible, + int help); + + mutable CSettingsManager *m_settingsManager; + std::shared_ptr<CSettingSection> m_section; +}; diff --git a/xbmc/settings/lib/CMakeLists.txt b/xbmc/settings/lib/CMakeLists.txt new file mode 100644 index 0000000..a39a0ca --- /dev/null +++ b/xbmc/settings/lib/CMakeLists.txt @@ -0,0 +1,31 @@ +set(SOURCES ISetting.cpp + ISettingControl.cpp + Setting.cpp + SettingCategoryAccess.cpp + SettingConditions.cpp + SettingDependency.cpp + SettingRequirement.cpp + SettingSection.cpp + SettingsManager.cpp + SettingUpdate.cpp) + +set(HEADERS ISetting.h + ISettingCallback.h + ISettingControl.h + ISettingControlCreator.h + ISettingCreator.h + ISettingsHandler.h + ISettingsValueSerializer.h + Setting.h + SettingCategoryAccess.h + SettingConditions.h + SettingDefinitions.h + SettingDependency.h + SettingLevel.h + SettingRequirement.h + SettingSection.h + SettingsManager.h + SettingType.h + SettingUpdate.h) + +core_add_library(settings_lib) diff --git a/xbmc/settings/lib/ISetting.cpp b/xbmc/settings/lib/ISetting.cpp new file mode 100644 index 0000000..a836f99 --- /dev/null +++ b/xbmc/settings/lib/ISetting.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ISetting.h" + +#include "SettingDefinitions.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" + +#include <string> + +ISetting::ISetting(const std::string &id, CSettingsManager *settingsManager /* = nullptr */) + : m_id(id) + , m_settingsManager(settingsManager) + , m_requirementCondition(settingsManager) +{ } + +bool ISetting::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (node == nullptr) + return false; + + bool value; + if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_VISIBLE, value)) + m_visible = value; + + auto element = node->ToElement(); + if (element == nullptr) + return false; + + int iValue = -1; + if (element->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &iValue) == TIXML_SUCCESS && iValue > 0) + m_label = iValue; + if (element->QueryIntAttribute(SETTING_XML_ATTR_HELP, &iValue) == TIXML_SUCCESS && iValue > 0) + m_help = iValue; + + auto requirementNode = node->FirstChild(SETTING_XML_ELM_REQUIREMENT); + if (requirementNode == nullptr) + return true; + + return m_requirementCondition.Deserialize(requirementNode); +} + +bool ISetting::DeserializeIdentification(const TiXmlNode* node, std::string& identification) +{ + return DeserializeIdentificationFromAttribute(node, SETTING_XML_ATTR_ID, identification); +} + +bool ISetting::DeserializeIdentificationFromAttribute(const TiXmlNode* node, + const std::string& attribute, + std::string& identification) +{ + if (node == nullptr) + return false; + + auto element = node->ToElement(); + if (element == nullptr) + return false; + + auto idAttribute = element->Attribute(attribute); + if (idAttribute == nullptr || idAttribute->empty()) + return false; + + identification = *idAttribute; + return true; +} + +void ISetting::CheckRequirements() +{ + m_meetsRequirements = m_requirementCondition.Check(); +} diff --git a/xbmc/settings/lib/ISetting.h b/xbmc/settings/lib/ISetting.h new file mode 100644 index 0000000..8a40fe3 --- /dev/null +++ b/xbmc/settings/lib/ISetting.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingRequirement.h" + +#include <string> + +class CSettingsManager; +class TiXmlNode; + +/*! + \ingroup settings + \brief Interface defining the base of all setting objects + */ +class ISetting +{ +public: + /*! + \brief Creates a new setting object with the given identifier. + + \param id Identifier of the setting object + \param settingsManager Reference to the settings manager + */ + ISetting(const std::string &id, CSettingsManager *settingsManager = nullptr); + virtual ~ISetting() = default; + + /*! + \brief Deserializes the given XML node into the properties of the setting + object. + + If the update parameter is true, the checks for mandatory properties are + skipped and values are only updated. + + \param node XML node containing the properties of the setting object + \param update Whether to perform checks for mandatory properties or not + \return True if deserialization was successful, false otherwise + */ + virtual bool Deserialize(const TiXmlNode *node, bool update = false); + + /*! + \brief Gets the identifier of the setting object. + + \return Identifier of the setting object + */ + const std::string& GetId() const { return m_id; } + /*! + \brief Whether the setting object is visible or hidden. + + \return True if the setting object is visible, false otherwise + */ + virtual bool IsVisible() const { return m_visible; } + /*! + \brief Sets the visibility state of the setting object. + + \param visible Whether the setting object shall be visible or not + */ + virtual void SetVisible(bool visible) { m_visible = visible; } + /*! + \brief Gets the localizeable label ID of the setting group. + + \return Localizeable label ID of the setting group + */ + int GetLabel() const { return m_label; } + /*! + \brief Sets the localizeable label ID of the setting group. + + \param label Localizeable label ID of the setting group + */ + void SetLabel(int label) { m_label = label; } + /*! + \brief Gets the localizeable help ID of the setting group. + + \return Localizeable help ID of the setting group + */ + int GetHelp() const { return m_help; } + /*! + \brief Sets the localizeable help ID of the setting group. + + \param label Localizeable help ID of the setting group + */ + void SetHelp(int help) { m_help = help; } + /*! + \brief Whether the setting object meets all necessary requirements. + + \return True if the setting object meets all necessary requirements, false otherwise + */ + virtual bool MeetsRequirements() const { return m_meetsRequirements; } + /*! + \brief Checks if the setting object meets all necessary requirements. + */ + virtual void CheckRequirements(); + /*! + \brief Sets whether the setting object meets all necessary requirements. + + \param visible Whether the setting object meets all necessary requirements or not + */ + virtual void SetRequirementsMet(bool requirementsMet) { m_meetsRequirements = requirementsMet; } + + /*! + \brief Deserializes the given XML node to retrieve a setting object's + identifier. + + \param node XML node containing a setting object's identifier + \param identification Will contain the deserialized setting object's identifier + \return True if a setting object's identifier was deserialized, false otherwise + */ + static bool DeserializeIdentification(const TiXmlNode *node, std::string &identification); + +protected: + static constexpr int DefaultLabel = -1; + /*! + \brief Deserializes the given XML node to retrieve a setting object's identifier from the given attribute. + + \param node XML node containing a setting object's identifier + \param attribute Attribute which contains the setting object's identifier + \param identification Will contain the deserialized setting object's identifier + \return True if a setting object's identifier was deserialized, false otherwise + */ + static bool DeserializeIdentificationFromAttribute(const TiXmlNode* node, + const std::string& attribute, + std::string& identification); + + std::string m_id; + CSettingsManager *m_settingsManager; + +private: + bool m_visible = true; + int m_label = DefaultLabel; + int m_help = -1; + bool m_meetsRequirements = true; + CSettingRequirement m_requirementCondition; +}; diff --git a/xbmc/settings/lib/ISettingCallback.h b/xbmc/settings/lib/ISettingCallback.h new file mode 100644 index 0000000..00fc428 --- /dev/null +++ b/xbmc/settings/lib/ISettingCallback.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> + +class CSetting; +class TiXmlNode; + +class ISettingCallback +{ +public: + virtual ~ISettingCallback() = default; + + /*! + \brief The value of the given setting is being changed. + + This callback is triggered whenever the value of a setting is being + changed. The given CSetting already contains the new value and the handler + of the callback has the possibility to allow or revert changing the value + of the setting. In case of a revert OnSettingChanging() is called again to + inform all listeners that the value change has been reverted. + + \param setting The setting whose value is being changed (already containing the changed value) + \return True if the new value is acceptable otherwise false + */ + virtual bool OnSettingChanging(const std::shared_ptr<const CSetting>& setting) { return true; } + + /*! + \brief The value of the given setting has changed. + + This callback is triggered whenever the value of a setting has been + successfully changed (i.e. none of the OnSettingChanging() handlers) + has reverted the change. + + \param setting The setting whose value has been changed + */ + virtual void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) {} + + /*! + \brief The given setting has been activated. + + This callback is triggered whenever the given setting has been activated. + This callback is only fired for CSettingAction settings. + + \param setting The setting which has been activated. + */ + virtual void OnSettingAction(const std::shared_ptr<const CSetting>& setting) {} + + /*! + \brief The given setting needs to be updated. + + This callback is triggered when a setting needs to be updated because its + value is outdated. This only happens when initially loading the value of a + setting and will not be triggered afterwards. + + \param setting The setting which needs to be updated. + \param oldSettingId The id of the previous setting. + \param oldSettingNode The old setting node + \return True if the setting has been successfully updated otherwise false + */ + virtual bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) + { + return false; + } + + /*! + \brief The given property of the given setting has changed + + This callback is triggered when a property (e.g. enabled or the list of + dynamic options) has changed. + + \param setting The setting which has a changed property + \param propertyName The string representation of the changed property + */ + virtual void OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) + { + } +}; diff --git a/xbmc/settings/lib/ISettingControl.cpp b/xbmc/settings/lib/ISettingControl.cpp new file mode 100644 index 0000000..3fc51a5 --- /dev/null +++ b/xbmc/settings/lib/ISettingControl.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ISettingControl.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +Logger ISettingControl::s_logger; + +ISettingControl::ISettingControl() +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("ISettingControl"); +} + +bool ISettingControl::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + if (node == nullptr) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + auto strTmp = elem->Attribute(SETTING_XML_ATTR_FORMAT); + std::string format; + if (strTmp != nullptr) + format = strTmp; + if (!SetFormat(format)) + { + s_logger->error("error reading \"{}\" attribute of <control>", SETTING_XML_ATTR_FORMAT); + return false; + } + + if ((strTmp = elem->Attribute(SETTING_XML_ATTR_DELAYED)) != nullptr) + { + if (!StringUtils::EqualsNoCase(strTmp, "false") && !StringUtils::EqualsNoCase(strTmp, "true")) + { + s_logger->error("error reading \"{}\" attribute of <control>", SETTING_XML_ATTR_DELAYED); + return false; + } + else + m_delayed = StringUtils::EqualsNoCase(strTmp, "true"); + } + + return true; +} diff --git a/xbmc/settings/lib/ISettingControl.h b/xbmc/settings/lib/ISettingControl.h new file mode 100644 index 0000000..293d5ad --- /dev/null +++ b/xbmc/settings/lib/ISettingControl.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/logtypes.h" + +#include <string> + +class TiXmlNode; + +class ISettingControl +{ +public: + ISettingControl(); + virtual ~ISettingControl() = default; + + virtual std::string GetType() const = 0; + const std::string& GetFormat() const { return m_format; } + bool GetDelayed() const { return m_delayed; } + void SetDelayed(bool delayed) { m_delayed = delayed; } + + virtual bool Deserialize(const TiXmlNode *node, bool update = false); + virtual bool SetFormat(const std::string &format) { return true; } + +protected: + bool m_delayed = false; + std::string m_format; + + static Logger s_logger; +}; diff --git a/xbmc/settings/lib/ISettingControlCreator.h b/xbmc/settings/lib/ISettingControlCreator.h new file mode 100644 index 0000000..2f77e1c --- /dev/null +++ b/xbmc/settings/lib/ISettingControlCreator.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class ISettingControl; + +/*! + \ingroup settings + \brief Interface for creating a new setting control of a custom setting control type. + */ +class ISettingControlCreator +{ +public: + virtual ~ISettingControlCreator() = default; + + /*! + \brief Creates a new setting control of the given custom setting control type. + + \param controlType string representation of the setting control type + \return A new setting control object of the given (custom) setting control type or nullptr if the setting control type is unknown + */ + virtual std::shared_ptr<ISettingControl> CreateControl(const std::string &controlType) const = 0; +}; diff --git a/xbmc/settings/lib/ISettingCreator.h b/xbmc/settings/lib/ISettingCreator.h new file mode 100644 index 0000000..e951c85 --- /dev/null +++ b/xbmc/settings/lib/ISettingCreator.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class CSetting; +class CSettingsManager; + +/*! + \ingroup settings + \brief Interface for creating a new setting of a custom setting type. + */ +class ISettingCreator +{ +public: + virtual ~ISettingCreator() = default; + + /*! + \brief Creates a new setting of the given custom setting type. + + \param settingType string representation of the setting type + \param settingId Identifier of the setting to be created + \param settingsManager Reference to the settings manager + \return A new setting object of the given (custom) setting type or nullptr if the setting type is unknown + */ + virtual std::shared_ptr<CSetting> CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager = nullptr) const = 0; +}; diff --git a/xbmc/settings/lib/ISettingsHandler.h b/xbmc/settings/lib/ISettingsHandler.h new file mode 100644 index 0000000..466b55a --- /dev/null +++ b/xbmc/settings/lib/ISettingsHandler.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! + \ingroup settings + \brief Interface defining methods being called by the settings system if an + action is performed on multiple/all settings + */ +class ISettingsHandler +{ +public: + virtual ~ISettingsHandler() = default; + + /*! + \brief Settings loading has been initiated. + + \return True if the settings should be loaded, false if the loading should be aborted. + */ + virtual bool OnSettingsLoading() { return true; } + /*! + \brief Settings have been loaded. + + This callback can be used to trigger loading other settings. + */ + virtual void OnSettingsLoaded() { } + /*! + \brief Settings saving has been initiated. + + \return True if the settings should be saved, false if the saving should be aborted. + */ + virtual bool OnSettingsSaving() const { return true; } + /*! + \brief Settings have been saved. + + This callback can be used to trigger saving other settings. + */ + virtual void OnSettingsSaved() const { } + /*! + \brief Setting values have been unloaded. + + This callback can be used to trigger uninitializing any state variables + (e.g. before re-loading the settings). + */ + virtual void OnSettingsUnloaded() { } + /*! + \brief Settings have been cleared. + + This callback can be used to trigger clearing any state variables. + */ + virtual void OnSettingsCleared() { } +}; diff --git a/xbmc/settings/lib/ISettingsValueSerializer.h b/xbmc/settings/lib/ISettingsValueSerializer.h new file mode 100644 index 0000000..94bf664 --- /dev/null +++ b/xbmc/settings/lib/ISettingsValueSerializer.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +class CSettingsManager; + +class ISettingsValueSerializer +{ +public: + virtual ~ISettingsValueSerializer() = default; + + virtual std::string SerializeValues(const CSettingsManager* settingsManager) const = 0; +}; diff --git a/xbmc/settings/lib/Setting.cpp b/xbmc/settings/lib/Setting.cpp new file mode 100644 index 0000000..0802dd8 --- /dev/null +++ b/xbmc/settings/lib/Setting.cpp @@ -0,0 +1,1690 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Setting.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/XMLUtils.h" +#include "utils/log.h" + +#include <mutex> +#include <shared_mutex> +#include <sstream> +#include <utility> + +template<typename TKey, typename TValue> +bool CheckSettingOptionsValidity(const TValue& value, const std::vector<std::pair<TKey, TValue>>& options) +{ + for (const auto& it : options) + { + if (it.second == value) + return true; + } + + return false; +} + +template<typename TKey, typename TValue> +bool CheckSettingOptionsValidity(const TValue& value, const std::vector<TKey>& options) +{ + for (const auto& it : options) + { + if (it.value == value) + return true; + } + + return false; +} + +bool DeserializeOptionsSort(const TiXmlElement* optionsElement, SettingOptionsSort& optionsSort) +{ + optionsSort = SettingOptionsSort::NoSorting; + + std::string sort; + if (optionsElement->QueryStringAttribute("sort", &sort) != TIXML_SUCCESS) + return true; + + if (StringUtils::EqualsNoCase(sort, "false") || StringUtils::EqualsNoCase(sort, "off") || + StringUtils::EqualsNoCase(sort, "no") || StringUtils::EqualsNoCase(sort, "disabled")) + optionsSort = SettingOptionsSort::NoSorting; + else if (StringUtils::EqualsNoCase(sort, "asc") || StringUtils::EqualsNoCase(sort, "ascending") || + StringUtils::EqualsNoCase(sort, "true") || StringUtils::EqualsNoCase(sort, "on") || + StringUtils::EqualsNoCase(sort, "yes") || StringUtils::EqualsNoCase(sort, "enabled")) + optionsSort = SettingOptionsSort::Ascending; + else if (StringUtils::EqualsNoCase(sort, "desc") || StringUtils::EqualsNoCase(sort, "descending")) + optionsSort = SettingOptionsSort::Descending; + else + return false; + + return true; +} + +Logger CSetting::s_logger; + +CSetting::CSetting(const std::string& id, CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSetting"); +} + +CSetting::CSetting(const std::string& id, const CSetting& setting) + : CSetting(id, setting.m_settingsManager) +{ + Copy(setting); +} + +void CSetting::MergeBasics(const CSetting& other) +{ + // ISetting + SetVisible(other.GetVisible()); + SetLabel(other.GetLabel()); + SetHelp(other.GetHelp()); + SetRequirementsMet(other.MeetsRequirements()); + // CSetting + SetEnabled(other.GetEnabled()); + SetParent(other.GetParent()); + SetLevel(other.GetLevel()); + SetControl(const_cast<CSetting&>(other).GetControl()); + SetDependencies(other.GetDependencies()); +} + +bool CSetting::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto element = node->ToElement(); + if (element == nullptr) + return false; + + auto parentSetting = element->Attribute(SETTING_XML_ATTR_PARENT); + if (parentSetting != nullptr) + m_parentSetting = parentSetting; + + // get <enable> + bool value; + if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_ENABLED, value)) + m_enabled = value; + + // get the <level> + int level = -1; + if (XMLUtils::GetInt(node, SETTING_XML_ELM_LEVEL, level)) + m_level = static_cast<SettingLevel>(level); + + if (m_level < SettingLevel::Basic || m_level > SettingLevel::Internal) + m_level = SettingLevel::Standard; + + auto dependencies = node->FirstChild(SETTING_XML_ELM_DEPENDENCIES); + if (dependencies != nullptr) + { + auto dependencyNode = dependencies->FirstChild(SETTING_XML_ELM_DEPENDENCY); + while (dependencyNode != nullptr) + { + CSettingDependency dependency(m_settingsManager); + if (dependency.Deserialize(dependencyNode)) + m_dependencies.push_back(dependency); + else + s_logger->warn("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_DEPENDENCY, m_id); + + dependencyNode = dependencyNode->NextSibling(SETTING_XML_ELM_DEPENDENCY); + } + } + + auto control = node->FirstChildElement(SETTING_XML_ELM_CONTROL); + if (control != nullptr) + { + auto controlType = control->Attribute(SETTING_XML_ATTR_TYPE); + if (controlType == nullptr) + { + s_logger->error("error reading \"{}\" attribute of <control> tag of \"{}\"", + SETTING_XML_ATTR_TYPE, m_id); + return false; + } + + m_control = m_settingsManager->CreateControl(controlType); + if (m_control == nullptr || !m_control->Deserialize(control, update)) + { + s_logger->error("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_CONTROL, m_id); + return false; + } + } + else if (!update && m_level < SettingLevel::Internal && !IsReference()) + { + s_logger->error("missing <{}> tag of \"{}\"", SETTING_XML_ELM_CONTROL, m_id); + return false; + } + + auto updates = node->FirstChild(SETTING_XML_ELM_UPDATES); + if (updates != nullptr) + { + auto updateElem = updates->FirstChildElement(SETTING_XML_ELM_UPDATE); + while (updateElem != nullptr) + { + CSettingUpdate settingUpdate; + if (settingUpdate.Deserialize(updateElem)) + { + if (!m_updates.insert(settingUpdate).second) + s_logger->warn("duplicate <{}> definition for \"{}\"", SETTING_XML_ELM_UPDATE, m_id); + } + else + s_logger->warn("error reading <{}> tag of \"{}\"", SETTING_XML_ELM_UPDATE, m_id); + + updateElem = updateElem->NextSiblingElement(SETTING_XML_ELM_UPDATE); + } + } + + return true; +} + +bool CSetting::IsEnabled() const +{ + if (m_dependencies.empty() && m_parentSetting.empty()) + return m_enabled; + + // if the setting has a parent setting and that parent setting is disabled + // the setting should automatically also be disabled + if (!m_parentSetting.empty()) + { + SettingPtr parentSetting = m_settingsManager->GetSetting(m_parentSetting); + if (parentSetting != nullptr && !parentSetting->IsEnabled()) + return false; + } + + bool enabled = m_enabled; + for (const auto& dep : m_dependencies) + { + if (dep.GetType() != SettingDependencyType::Enable) + continue; + + if (!dep.Check()) + { + enabled = false; + break; + } + } + + return enabled; +} + +void CSetting::SetEnabled(bool enabled) +{ + if (!m_dependencies.empty() || m_enabled == enabled) + return; + + m_enabled = enabled; + OnSettingPropertyChanged(shared_from_this(), "enabled"); +} + +void CSetting::MakeReference(const std::string& referencedId /* = "" */) +{ + auto tmpReferencedId = referencedId; + if (referencedId.empty()) + tmpReferencedId = m_id; + + m_id = StringUtils::Format("#{}[{}]", tmpReferencedId, StringUtils::CreateUUID()); + m_referencedId = tmpReferencedId; +} + +bool CSetting::IsVisible() const +{ + if (!ISetting::IsVisible()) + return false; + + bool visible = true; + for (const auto& dep : m_dependencies) + { + if (dep.GetType() != SettingDependencyType::Visible) + continue; + + if (!dep.Check()) + { + visible = false; + break; + } + } + + return visible; +} + +bool CSetting::OnSettingChanging(const std::shared_ptr<const CSetting>& setting) +{ + if (m_callback == nullptr) + return true; + + return m_callback->OnSettingChanging(setting); +} + +void CSetting::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (m_callback == nullptr) + return; + + m_callback->OnSettingChanged(setting); +} + +void CSetting::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (m_callback == nullptr) + return; + + m_callback->OnSettingAction(setting); +} + +bool CSetting::DeserializeIdentification(const TiXmlNode* node, + std::string& identification, + bool& isReference) +{ + isReference = false; + + // first check if we can simply retrieve the setting's identifier + if (ISetting::DeserializeIdentification(node, identification)) + return true; + + // otherwise try to retrieve a reference to another setting's identifier + if (!DeserializeIdentificationFromAttribute(node, SETTING_XML_ATTR_REFERENCE, identification)) + return false; + + isReference = true; + return true; +} + +bool CSetting::OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) +{ + if (m_callback == nullptr) + return false; + + return m_callback->OnSettingUpdate(setting, oldSettingId, oldSettingNode); +} + +void CSetting::OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) +{ + if (m_callback == nullptr) + return; + + m_callback->OnSettingPropertyChanged(setting, propertyName); +} + +void CSetting::Copy(const CSetting &setting) +{ + SetVisible(setting.IsVisible()); + SetLabel(setting.GetLabel()); + SetHelp(setting.GetHelp()); + SetRequirementsMet(setting.MeetsRequirements()); + m_callback = setting.m_callback; + m_level = setting.m_level; + + if (setting.m_control != nullptr) + { + m_control = m_settingsManager->CreateControl(setting.m_control->GetType()); + *m_control = *setting.m_control; + } + else + m_control = nullptr; + + m_dependencies = setting.m_dependencies; + m_updates = setting.m_updates; + m_changed = setting.m_changed; +} + +Logger CSettingList::s_logger; + +CSettingList::CSettingList(const std::string& id, + std::shared_ptr<CSetting> settingDefinition, + CSettingsManager* settingsManager /* = nullptr */) + : CSetting(id, settingsManager), m_definition(std::move(settingDefinition)) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingList"); +} + +CSettingList::CSettingList(const std::string& id, + std::shared_ptr<CSetting> settingDefinition, + int label, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingList(id, std::move(settingDefinition), settingsManager) +{ + SetLabel(label); +} + +CSettingList::CSettingList(const std::string &id, const CSettingList &setting) + : CSetting(id, setting) +{ + copy(setting); +} + +SettingPtr CSettingList::Clone(const std::string &id) const +{ + if (m_definition == nullptr) + return nullptr; + + return std::make_shared<CSettingList>(id, *this); +} + +void CSettingList::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::List) + return; + + const auto& listSetting = static_cast<const CSettingList&>(other); + if (m_definition == nullptr && listSetting.m_definition != nullptr) + m_definition = listSetting.m_definition; + if (m_defaults.empty() && !listSetting.m_defaults.empty()) + m_defaults = listSetting.m_defaults; + if (m_values.empty() && !listSetting.m_values.empty()) + m_values = listSetting.m_values; + if (m_delimiter == "|" && listSetting.m_delimiter != "|") + m_delimiter = listSetting.m_delimiter; + if (m_minimumItems == 0 && listSetting.m_minimumItems != 0) + m_minimumItems = listSetting.m_minimumItems; + if (m_maximumItems == -1 && listSetting.m_maximumItems != -1) + m_maximumItems = listSetting.m_maximumItems; +} + +bool CSettingList::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (m_definition == nullptr) + return false; + + if (!CSetting::Deserialize(node, update)) + return false; + + auto element = node->ToElement(); + if (element == nullptr) + { + s_logger->warn("unable to read type of list setting of {}", m_id); + return false; + } + + // deserialize the setting definition in update mode because we don't care + // about an invalid <default> value (which is never used) + if (!m_definition->Deserialize(node, true)) + return false; + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // read the delimiter + std::string delimiter; + if (XMLUtils::GetString(constraints, SETTING_XML_ELM_DELIMITER, delimiter) && !delimiter.empty()) + m_delimiter = delimiter; + + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MINIMUM_ITEMS, m_minimumItems); + if (m_minimumItems < 0) + m_minimumItems = 0; + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MAXIMUM_ITEMS, m_maximumItems); + if (m_maximumItems <= 0) + m_maximumItems = -1; + else if (m_maximumItems < m_minimumItems) + { + s_logger->warn("invalid <{}> ({}) and/or <{}> ({}) of {}", SETTING_XML_ELM_MINIMUM_ITEMS, + m_minimumItems, SETTING_XML_ELM_MAXIMUM_ITEMS, m_maximumItems, m_id); + return false; + } + } + + // read the default and initial values + std::string values; + if (XMLUtils::GetString(node, SETTING_XML_ELM_DEFAULT, values)) + { + if (!fromString(values, m_defaults)) + { + s_logger->warn("invalid <{}> definition \"{}\" of {}", SETTING_XML_ELM_DEFAULT, values, m_id); + return false; + } + Reset(); + } + + return true; +} + +SettingType CSettingList::GetElementType() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + + if (m_definition == nullptr) + return SettingType::Unknown; + + return m_definition->GetType(); +} + +bool CSettingList::FromString(const std::string &value) +{ + SettingList values; + if (!fromString(value, values)) + return false; + + return SetValue(values); +} + +std::string CSettingList::ToString() const +{ + return toString(m_values); +} + +bool CSettingList::Equals(const std::string &value) const +{ + SettingList values; + if (!fromString(value, values) || values.size() != m_values.size()) + return false; + + bool ret = true; + for (size_t index = 0; index < values.size(); index++) + { + if (!m_values[index]->Equals(values[index]->ToString())) + { + ret = false; + break; + } + } + + return ret; +} + +bool CSettingList::CheckValidity(const std::string &value) const +{ + SettingList values; + return fromString(value, values); +} + +void CSettingList::Reset() +{ + std::unique_lock<CSharedSection> lock(m_critical); + SettingList values; + for (const auto& it : m_defaults) + values.push_back(it->Clone(it->GetId())); + + SetValue(values); +} + +bool CSettingList::FromString(const std::vector<std::string> &value) +{ + SettingList values; + if (!fromValues(value, values)) + return false; + + return SetValue(values); +} + +bool CSettingList::SetValue(const SettingList &values) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if ((int)values.size() < m_minimumItems || + (m_maximumItems > 0 && (int)values.size() > m_maximumItems)) + return false; + + bool equal = values.size() == m_values.size(); + for (size_t index = 0; index < values.size(); index++) + { + if (values[index]->GetType() != GetElementType()) + return false; + + if (equal && + !values[index]->Equals(m_values[index]->ToString())) + equal = false; + } + + if (equal) + return true; + + SettingList oldValues = m_values; + m_values.clear(); + m_values.insert(m_values.begin(), values.begin(), values.end()); + + if (!OnSettingChanging(shared_from_base<CSettingList>())) + { + m_values = oldValues; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingList>()); + return false; + } + + m_changed = toString(m_values) != toString(m_defaults); + OnSettingChanged(shared_from_base<CSettingList>()); + return true; +} + +void CSettingList::SetDefault(const SettingList &values) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_defaults.clear(); + m_defaults.insert(m_defaults.begin(), values.begin(), values.end()); + + if (!m_changed) + { + m_values.clear(); + for (const auto& it : m_defaults) + m_values.push_back(it->Clone(it->GetId())); + } +} + +void CSettingList::copy(const CSettingList &setting) +{ + CSetting::Copy(setting); + + copy(setting.m_values, m_values); + copy(setting.m_defaults, m_defaults); + + if (setting.m_definition != nullptr) + { + auto definitionCopy = setting.m_definition->Clone(m_id + ".definition"); + if (definitionCopy != nullptr) + m_definition = definitionCopy; + } + + m_delimiter = setting.m_delimiter; + m_minimumItems = setting.m_minimumItems; + m_maximumItems = setting.m_maximumItems; +} + +void CSettingList::copy(const SettingList &srcValues, SettingList &dstValues) +{ + dstValues.clear(); + + for (const auto& value : srcValues) + { + if (value == nullptr) + continue; + + SettingPtr valueCopy = value->Clone(value->GetId()); + if (valueCopy == nullptr) + continue; + + dstValues.emplace_back(valueCopy); + } +} + +bool CSettingList::fromString(const std::string &strValue, SettingList &values) const +{ + return fromValues(StringUtils::Split(strValue, m_delimiter), values); +} + +bool CSettingList::fromValues(const std::vector<std::string> &strValues, SettingList &values) const +{ + if ((int)strValues.size() < m_minimumItems || + (m_maximumItems > 0 && (int)strValues.size() > m_maximumItems)) + return false; + + bool ret = true; + int index = 0; + for (const auto& value : strValues) + { + auto settingValue = m_definition->Clone(StringUtils::Format("{}.{}", m_id, index++)); + if (settingValue == nullptr || + !settingValue->FromString(value)) + { + ret = false; + break; + } + + values.emplace_back(settingValue); + } + + if (!ret) + values.clear(); + + return ret; +} + +std::string CSettingList::toString(const SettingList &values) const +{ + std::vector<std::string> strValues; + for (const auto& value : values) + { + if (value != nullptr) + strValues.push_back(value->ToString()); + } + + return StringUtils::Join(strValues, m_delimiter); +} + +Logger CSettingBool::s_logger; + +CSettingBool::CSettingBool(const std::string& id, CSettingsManager* settingsManager /* = nullptr */) + : CSettingBool(id, DefaultLabel, DefaultValue, settingsManager) +{ +} + +CSettingBool::CSettingBool(const std::string& id, const CSettingBool& setting) + : CSettingBool(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingBool::CSettingBool(const std::string& id, + int label, + bool value, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), m_value(value), m_default(value) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingBool"); +} + +SettingPtr CSettingBool::Clone(const std::string &id) const +{ + return std::make_shared<CSettingBool>(id, *this); +} + +void CSettingBool::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Boolean) + return; + + const auto& boolSetting = static_cast<const CSettingBool&>(other); + if (m_default == false && boolSetting.m_default == true) + m_default = boolSetting.m_default; + if (m_value == m_default && boolSetting.m_value != m_default) + m_value = boolSetting.m_value; +} + +bool CSettingBool::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + // get the default value + bool value; + if (XMLUtils::GetBoolean(node, SETTING_XML_ELM_DEFAULT, value)) + m_value = m_default = value; + else if (!update) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + return true; +} + +bool CSettingBool::FromString(const std::string &value) +{ + bool bValue; + if (!fromString(value, bValue)) + return false; + + return SetValue(bValue); +} + +std::string CSettingBool::ToString() const +{ + return m_value ? "true" : "false"; +} + +bool CSettingBool::Equals(const std::string &value) const +{ + bool bValue; + return (fromString(value, bValue) && m_value == bValue); +} + +bool CSettingBool::CheckValidity(const std::string &value) const +{ + bool bValue; + return fromString(value, bValue); +} + +bool CSettingBool::SetValue(bool value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + bool oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingBool>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingBool>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingBool>()); + return true; +} + +void CSettingBool::SetDefault(bool value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +void CSettingBool::copy(const CSettingBool &setting) +{ + CSetting::Copy(setting); + + m_value = setting.m_value; + m_default = setting.m_default; +} + +bool CSettingBool::fromString(const std::string &strValue, bool &value) const +{ + if (StringUtils::EqualsNoCase(strValue, "true")) + { + value = true; + return true; + } + if (StringUtils::EqualsNoCase(strValue, "false")) + { + value = false; + return true; + } + + return false; +} + +Logger CSettingInt::s_logger; + +CSettingInt::CSettingInt(const std::string& id, CSettingsManager* settingsManager /* = nullptr */) + : CSettingInt(id, DefaultLabel, DefaultValue, settingsManager) +{ } + +CSettingInt::CSettingInt(const std::string& id, const CSettingInt& setting) + : CSettingInt(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingInt::CSettingInt(const std::string& id, + int label, + int value, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingInt(id, label, value, DefaultMin, DefaultStep, DefaultMax, settingsManager) +{ + SetLabel(label); +} + +CSettingInt::CSettingInt(const std::string& id, + int label, + int value, + int minimum, + int step, + int maximum, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), + m_value(value), + m_default(value), + m_min(minimum), + m_step(step), + m_max(maximum) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingInt"); +} + +CSettingInt::CSettingInt(const std::string& id, + int label, + int value, + const TranslatableIntegerSettingOptions& options, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingInt(id, label, value, settingsManager) +{ + SetTranslatableOptions(options); +} + +SettingPtr CSettingInt::Clone(const std::string &id) const +{ + return std::make_shared<CSettingInt>(id, *this); +} + +void CSettingInt::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Integer) + return; + + const auto& intSetting = static_cast<const CSettingInt&>(other); + if (m_default == 0.0 && intSetting.m_default != 0.0) + m_default = intSetting.m_default; + if (m_value == m_default && intSetting.m_value != m_default) + m_value = intSetting.m_value; + if (m_min == 0.0 && intSetting.m_min != 0.0) + m_min = intSetting.m_min; + if (m_step == 1.0 && intSetting.m_step != 1.0) + m_step = intSetting.m_step; + if (m_max == 0.0 && intSetting.m_max != 0.0) + m_max = intSetting.m_max; + if (m_translatableOptions.empty() && !intSetting.m_translatableOptions.empty()) + m_translatableOptions = intSetting.m_translatableOptions; + if (m_options.empty() && !intSetting.m_options.empty()) + m_options = intSetting.m_options; + if (m_optionsFillerName.empty() && !intSetting.m_optionsFillerName.empty()) + m_optionsFillerName = intSetting.m_optionsFillerName; + if (m_optionsFiller == nullptr && intSetting.m_optionsFiller != nullptr) + m_optionsFiller = intSetting.m_optionsFiller; + if (m_optionsFillerData == nullptr && intSetting.m_optionsFillerData != nullptr) + m_optionsFillerData = intSetting.m_optionsFillerData; + if (m_dynamicOptions.empty() && !intSetting.m_dynamicOptions.empty()) + m_dynamicOptions = intSetting.m_dynamicOptions; + if (m_optionsSort == SettingOptionsSort::NoSorting && + intSetting.m_optionsSort != SettingOptionsSort::NoSorting) + m_optionsSort = intSetting.m_optionsSort; +} + +bool CSettingInt::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + // get the default value + int value; + if (XMLUtils::GetInt(node, SETTING_XML_ELM_DEFAULT, value)) + m_value = m_default = value; + else if (!update) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // get the entries + auto options = constraints->FirstChildElement(SETTING_XML_ELM_OPTIONS); + if (options != nullptr && options->FirstChild() != nullptr) + { + if (!DeserializeOptionsSort(options, m_optionsSort)) + s_logger->warn("invalid \"sort\" attribute of <" SETTING_XML_ELM_OPTIONS "> for \"{}\"", + m_id); + + if (options->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT) + { + m_optionsFillerName = options->FirstChild()->ValueStr(); + if (!m_optionsFillerName.empty()) + { + m_optionsFiller = reinterpret_cast<IntegerSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingInt>())); + } + } + else + { + m_translatableOptions.clear(); + auto optionElement = options->FirstChildElement(SETTING_XML_ELM_OPTION); + while (optionElement != nullptr) + { + TranslatableIntegerSettingOption entry; + if (optionElement->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &entry.label) == + TIXML_SUCCESS && + entry.label > 0) + { + entry.value = strtol(optionElement->FirstChild()->Value(), nullptr, 10); + m_translatableOptions.push_back(entry); + } + else + { + std::string label; + if (optionElement->QueryStringAttribute(SETTING_XML_ATTR_LABEL, &label) == + TIXML_SUCCESS) + { + int value = strtol(optionElement->FirstChild()->Value(), nullptr, 10); + m_options.emplace_back(label, value); + } + } + + optionElement = optionElement->NextSiblingElement(SETTING_XML_ELM_OPTION); + } + } + } + + // get minimum + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MINIMUM, m_min); + // get step + XMLUtils::GetInt(constraints, SETTING_XML_ELM_STEP, m_step); + // get maximum + XMLUtils::GetInt(constraints, SETTING_XML_ELM_MAXIMUM, m_max); + } + + return true; +} + +bool CSettingInt::FromString(const std::string &value) +{ + int iValue; + if (!fromString(value, iValue)) + return false; + + return SetValue(iValue); +} + +std::string CSettingInt::ToString() const +{ + std::ostringstream oss; + oss << m_value; + + return oss.str(); +} + +bool CSettingInt::Equals(const std::string &value) const +{ + int iValue; + return (fromString(value, iValue) && m_value == iValue); +} + +bool CSettingInt::CheckValidity(const std::string &value) const +{ + int iValue; + if (!fromString(value, iValue)) + return false; + + return CheckValidity(iValue); +} + +bool CSettingInt::CheckValidity(int value) const +{ + if (!m_translatableOptions.empty()) + { + if (!CheckSettingOptionsValidity(value, m_translatableOptions)) + return false; + } + else if (!m_options.empty()) + { + if (!CheckSettingOptionsValidity(value, m_options)) + return false; + } + else if (m_optionsFillerName.empty() && m_optionsFiller == nullptr && + m_min != m_max && (value < m_min || value > m_max)) + return false; + + return true; +} + +bool CSettingInt::SetValue(int value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + if (!CheckValidity(value)) + return false; + + int oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingInt>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingInt>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingInt>()); + return true; +} + +void CSettingInt::SetDefault(int value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +SettingOptionsType CSettingInt::GetOptionsType() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (!m_translatableOptions.empty()) + return SettingOptionsType::StaticTranslatable; + if (!m_options.empty()) + return SettingOptionsType::Static; + if (!m_optionsFillerName.empty() || m_optionsFiller != nullptr) + return SettingOptionsType::Dynamic; + + return SettingOptionsType::Unknown; +} + +IntegerSettingOptions CSettingInt::UpdateDynamicOptions() +{ + std::unique_lock<CSharedSection> lock(m_critical); + IntegerSettingOptions options; + if (m_optionsFiller == nullptr && + (m_optionsFillerName.empty() || m_settingsManager == nullptr)) + return options; + + if (m_optionsFiller == nullptr) + { + m_optionsFiller = reinterpret_cast<IntegerSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingInt>())); + if (m_optionsFiller == nullptr) + { + s_logger->warn("unknown options filler \"{}\" of \"{}\"", m_optionsFillerName, m_id); + return options; + } + } + + int bestMatchingValue = m_value; + m_optionsFiller(shared_from_base<CSettingInt>(), options, bestMatchingValue, m_optionsFillerData); + + if (bestMatchingValue != m_value) + SetValue(bestMatchingValue); + + bool changed = m_dynamicOptions.size() != options.size(); + if (!changed) + { + for (size_t index = 0; index < options.size(); index++) + { + if (options[index].label.compare(m_dynamicOptions[index].label) != 0 || + options[index].value != m_dynamicOptions[index].value) + { + changed = true; + break; + } + } + } + + if (changed) + { + m_dynamicOptions = options; + OnSettingPropertyChanged(shared_from_base<CSettingInt>(), "options"); + } + + return options; +} + +void CSettingInt::copy(const CSettingInt &setting) +{ + CSetting::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + + m_value = setting.m_value; + m_default = setting.m_default; + m_min = setting.m_min; + m_step = setting.m_step; + m_max = setting.m_max; + m_translatableOptions = setting.m_translatableOptions; + m_options = setting.m_options; + m_optionsFillerName = setting.m_optionsFillerName; + m_optionsFiller = setting.m_optionsFiller; + m_optionsFillerData = setting.m_optionsFillerData; + m_dynamicOptions = setting.m_dynamicOptions; +} + +bool CSettingInt::fromString(const std::string &strValue, int &value) +{ + if (strValue.empty()) + return false; + + char *end = nullptr; + value = (int)strtol(strValue.c_str(), &end, 10); + if (end != nullptr && *end != '\0') + return false; + + return true; +} + +Logger CSettingNumber::s_logger; + +CSettingNumber::CSettingNumber(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingNumber(id, DefaultLabel, DefaultValue, settingsManager) +{ } + +CSettingNumber::CSettingNumber(const std::string& id, const CSettingNumber& setting) + : CSettingNumber(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingNumber::CSettingNumber(const std::string& id, + int label, + float value, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingNumber(id, label, value, DefaultMin, DefaultStep, DefaultMax, settingsManager) +{ +} + +CSettingNumber::CSettingNumber(const std::string& id, + int label, + float value, + float minimum, + float step, + float maximum, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), + m_value(static_cast<double>(value)), + m_default(static_cast<double>(value)), + m_min(static_cast<double>(minimum)), + m_step(static_cast<double>(step)), + m_max(static_cast<double>(maximum)) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingNumber"); +} + +SettingPtr CSettingNumber::Clone(const std::string &id) const +{ + return std::make_shared<CSettingNumber>(id, *this); +} + +void CSettingNumber::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Number) + return; + + const auto& numberSetting = static_cast<const CSettingNumber&>(other); + if (m_default == 0.0 && numberSetting.m_default != 0.0) + m_default = numberSetting.m_default; + if (m_value == m_default && numberSetting.m_value != m_default) + m_value = numberSetting.m_value; + if (m_min == 0.0 && numberSetting.m_min != 0.0) + m_min = numberSetting.m_min; + if (m_step == 1.0 && numberSetting.m_step != 1.0) + m_step = numberSetting.m_step; + if (m_max == 0.0 && numberSetting.m_max != 0.0) + m_max = numberSetting.m_max; +} + +bool CSettingNumber::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + // get the default value + double value; + if (XMLUtils::GetDouble(node, SETTING_XML_ELM_DEFAULT, value)) + m_value = m_default = value; + else if (!update) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // get the minimum value + XMLUtils::GetDouble(constraints, SETTING_XML_ELM_MINIMUM, m_min); + // get the step value + XMLUtils::GetDouble(constraints, SETTING_XML_ELM_STEP, m_step); + // get the maximum value + XMLUtils::GetDouble(constraints, SETTING_XML_ELM_MAXIMUM, m_max); + } + + return true; +} + +bool CSettingNumber::FromString(const std::string &value) +{ + double dValue; + if (!fromString(value, dValue)) + return false; + + return SetValue(dValue); +} + +std::string CSettingNumber::ToString() const +{ + std::ostringstream oss; + oss << m_value; + + return oss.str(); +} + +bool CSettingNumber::Equals(const std::string &value) const +{ + double dValue; + std::shared_lock<CSharedSection> lock(m_critical); + return (fromString(value, dValue) && m_value == dValue); +} + +bool CSettingNumber::CheckValidity(const std::string &value) const +{ + double dValue; + if (!fromString(value, dValue)) + return false; + + return CheckValidity(dValue); +} + +bool CSettingNumber::CheckValidity(double value) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (m_min != m_max && + (value < m_min || value > m_max)) + return false; + + return true; +} + +bool CSettingNumber::SetValue(double value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + if (!CheckValidity(value)) + return false; + + double oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingNumber>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingNumber>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingNumber>()); + return true; +} + +void CSettingNumber::SetDefault(double value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +void CSettingNumber::copy(const CSettingNumber &setting) +{ + CSetting::Copy(setting); + std::unique_lock<CSharedSection> lock(m_critical); + + m_value = setting.m_value; + m_default = setting.m_default; + m_min = setting.m_min; + m_step = setting.m_step; + m_max = setting.m_max; +} + +bool CSettingNumber::fromString(const std::string &strValue, double &value) +{ + if (strValue.empty()) + return false; + + char *end = nullptr; + value = strtod(strValue.c_str(), &end); + if (end != nullptr && *end != '\0') + return false; + + return true; +} + +const CSettingString::Value CSettingString::DefaultValue; +Logger CSettingString::s_logger; + +CSettingString::CSettingString(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingString(id, DefaultLabel, DefaultValue, settingsManager) +{ } + +CSettingString::CSettingString(const std::string& id, const CSettingString& setting) + : CSettingString(id, setting.m_settingsManager) +{ + copy(setting); +} + +CSettingString::CSettingString(const std::string& id, + int label, + const std::string& value, + CSettingsManager* settingsManager /* = nullptr */) + : CTraitedSetting(id, settingsManager), m_value(value), m_default(value) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingString"); +} + +SettingPtr CSettingString::Clone(const std::string &id) const +{ + return std::make_shared<CSettingString>(id, *this); +} + +void CSettingString::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::String) + return; + + const auto& stringSetting = static_cast<const CSettingString&>(other); + if (m_default.empty() && !stringSetting.m_default.empty()) + m_default = stringSetting.m_default; + if (m_value == m_default && stringSetting.m_value != m_default) + m_value = stringSetting.m_value; + if (m_allowEmpty == false && stringSetting.m_allowEmpty == true) + m_allowEmpty = stringSetting.m_allowEmpty; + if (m_allowNewOption == false && stringSetting.m_allowNewOption == true) + m_allowNewOption = stringSetting.m_allowNewOption; + if (m_translatableOptions.empty() && !stringSetting.m_translatableOptions.empty()) + m_translatableOptions = stringSetting.m_translatableOptions; + if (m_options.empty() && !stringSetting.m_options.empty()) + m_options = stringSetting.m_options; + if (m_optionsFillerName.empty() && !stringSetting.m_optionsFillerName.empty()) + m_optionsFillerName = stringSetting.m_optionsFillerName; + if (m_optionsFiller == nullptr && stringSetting.m_optionsFiller != nullptr) + m_optionsFiller = stringSetting.m_optionsFiller; + if (m_optionsFillerData == nullptr && stringSetting.m_optionsFillerData != nullptr) + m_optionsFillerData = stringSetting.m_optionsFillerData; + if (m_dynamicOptions.empty() && !stringSetting.m_dynamicOptions.empty()) + m_dynamicOptions = stringSetting.m_dynamicOptions; + if (m_optionsSort == SettingOptionsSort::NoSorting && + stringSetting.m_optionsSort != SettingOptionsSort::NoSorting) + m_optionsSort = stringSetting.m_optionsSort; +} + +bool CSettingString::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + auto constraints = node->FirstChild(SETTING_XML_ELM_CONSTRAINTS); + if (constraints != nullptr) + { + // get allowempty (needs to be parsed before parsing the default value) + XMLUtils::GetBoolean(constraints, SETTING_XML_ELM_ALLOWEMPTY, m_allowEmpty); + + // Values other than those in options constraints allowed to be added + XMLUtils::GetBoolean(constraints, SETTING_XML_ELM_ALLOWNEWOPTION, m_allowNewOption); + + // get the entries + auto options = constraints->FirstChildElement(SETTING_XML_ELM_OPTIONS); + if (options != nullptr && options->FirstChild() != nullptr) + { + if (!DeserializeOptionsSort(options, m_optionsSort)) + s_logger->warn("invalid \"sort\" attribute of <" SETTING_XML_ELM_OPTIONS "> for \"{}\"", + m_id); + + if (options->FirstChild()->Type() == TiXmlNode::TINYXML_TEXT) + { + m_optionsFillerName = options->FirstChild()->ValueStr(); + if (!m_optionsFillerName.empty()) + { + m_optionsFiller = reinterpret_cast<StringSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingString>())); + } + } + else + { + m_translatableOptions.clear(); + auto optionElement = options->FirstChildElement(SETTING_XML_ELM_OPTION); + while (optionElement != nullptr) + { + TranslatableStringSettingOption entry; + if (optionElement->QueryIntAttribute(SETTING_XML_ATTR_LABEL, &entry.first) == TIXML_SUCCESS && entry.first > 0) + { + entry.second = optionElement->FirstChild()->Value(); + m_translatableOptions.push_back(entry); + } + else + { + const std::string value = optionElement->FirstChild()->Value(); + // if a specific "label" attribute is present use it otherwise use the value as label + std::string label = value; + optionElement->QueryStringAttribute(SETTING_XML_ATTR_LABEL, &label); + + m_options.emplace_back(label, value); + } + + optionElement = optionElement->NextSiblingElement(SETTING_XML_ELM_OPTION); + } + } + } + } + + // get the default value + std::string value; + if (XMLUtils::GetString(node, SETTING_XML_ELM_DEFAULT, value) && + (!value.empty() || m_allowEmpty)) + m_value = m_default = value; + else if (!update && !m_allowEmpty) + { + s_logger->error("error reading the default value of \"{}\"", m_id); + return false; + } + + return true; +} + +bool CSettingString::CheckValidity(const std::string &value) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (!m_allowEmpty && value.empty()) + return false; + + if (!m_translatableOptions.empty()) + { + if (!CheckSettingOptionsValidity(value, m_translatableOptions)) + return false; + } + else if (!m_options.empty() && !m_allowNewOption) + { + if (!CheckSettingOptionsValidity(value, m_options)) + return false; + } + + return true; +} + +bool CSettingString::SetValue(const std::string &value) +{ + std::unique_lock<CSharedSection> lock(m_critical); + + if (value == m_value) + return true; + + if (!CheckValidity(value)) + return false; + + std::string oldValue = m_value; + m_value = value; + + if (!OnSettingChanging(shared_from_base<CSettingString>())) + { + m_value = oldValue; + + // the setting couldn't be changed because one of the + // callback handlers failed the OnSettingChanging() + // callback so we need to let all the callback handlers + // know that the setting hasn't changed + OnSettingChanging(shared_from_base<CSettingString>()); + return false; + } + + m_changed = m_value != m_default; + OnSettingChanged(shared_from_base<CSettingString>()); + return true; +} + +void CSettingString::SetDefault(const std::string &value) +{ + std::shared_lock<CSharedSection> lock(m_critical); + + m_default = value; + if (!m_changed) + m_value = m_default; +} + +SettingOptionsType CSettingString::GetOptionsType() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (!m_translatableOptions.empty()) + return SettingOptionsType::StaticTranslatable; + if (!m_options.empty()) + return SettingOptionsType::Static; + if (!m_optionsFillerName.empty() || m_optionsFiller != nullptr) + return SettingOptionsType::Dynamic; + + return SettingOptionsType::Unknown; +} + +StringSettingOptions CSettingString::UpdateDynamicOptions() +{ + std::unique_lock<CSharedSection> lock(m_critical); + StringSettingOptions options; + if (m_optionsFiller == nullptr && + (m_optionsFillerName.empty() || m_settingsManager == nullptr)) + return options; + + if (m_optionsFiller == nullptr) + { + m_optionsFiller = reinterpret_cast<StringSettingOptionsFiller>(m_settingsManager->GetSettingOptionsFiller(shared_from_base<CSettingString>())); + if (m_optionsFiller == nullptr) + { + s_logger->error("unknown options filler \"{}\" of \"{}\"", m_optionsFillerName, m_id); + return options; + } + } + + std::string bestMatchingValue = m_value; + m_optionsFiller(shared_from_base<CSettingString>(), options, bestMatchingValue, m_optionsFillerData); + + if (bestMatchingValue != m_value) + SetValue(bestMatchingValue); + + // check if the list of items has changed + bool changed = m_dynamicOptions.size() != options.size(); + if (!changed) + { + for (size_t index = 0; index < options.size(); index++) + { + if (options[index].label.compare(m_dynamicOptions[index].label) != 0 || + options[index].value.compare(m_dynamicOptions[index].value) != 0) + { + changed = true; + break; + } + } + } + + if (changed) + { + m_dynamicOptions = options; + OnSettingPropertyChanged(shared_from_base<CSettingString>(), "options"); + } + + return options; +} + +void CSettingString::copy(const CSettingString &setting) +{ + CSetting::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + m_value = setting.m_value; + m_default = setting.m_default; + m_allowEmpty = setting.m_allowEmpty; + m_allowNewOption = setting.m_allowNewOption; + m_translatableOptions = setting.m_translatableOptions; + m_options = setting.m_options; + m_optionsFillerName = setting.m_optionsFillerName; + m_optionsFiller = setting.m_optionsFiller; + m_optionsFillerData = setting.m_optionsFillerData; + m_dynamicOptions = setting.m_dynamicOptions; +} + +Logger CSettingAction::s_logger; + +CSettingAction::CSettingAction(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingAction(id, DefaultLabel, settingsManager) +{ } + +CSettingAction::CSettingAction(const std::string& id, + int label, + CSettingsManager* settingsManager /* = nullptr */) + : CSetting(id, settingsManager) +{ + SetLabel(label); + + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingAction"); +} + +CSettingAction::CSettingAction(const std::string& id, const CSettingAction& setting) + : CSettingAction(id, setting.m_settingsManager) +{ + copy(setting); +} + +SettingPtr CSettingAction::Clone(const std::string &id) const +{ + return std::make_shared<CSettingAction>(id, *this); +} + +void CSettingAction::MergeDetails(const CSetting& other) +{ + if (other.GetType() != SettingType::Action) + return; + + const auto& actionSetting = static_cast<const CSettingAction&>(other); + if (!HasData() && actionSetting.HasData()) + SetData(actionSetting.GetData()); +} + +bool CSettingAction::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + std::shared_lock<CSharedSection> lock(m_critical); + + if (!CSetting::Deserialize(node, update)) + return false; + + m_data = XMLUtils::GetString(node, SETTING_XML_ELM_DATA); + + return true; +} + +void CSettingAction::copy(const CSettingAction& setting) +{ + CSetting::Copy(setting); + + std::unique_lock<CSharedSection> lock(m_critical); + m_data = setting.m_data; +} diff --git a/xbmc/settings/lib/Setting.h b/xbmc/settings/lib/Setting.h new file mode 100644 index 0000000..9e3ed3f --- /dev/null +++ b/xbmc/settings/lib/Setting.h @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISetting.h" +#include "ISettingCallback.h" +#include "ISettingControl.h" +#include "SettingDefinitions.h" +#include "SettingDependency.h" +#include "SettingLevel.h" +#include "SettingType.h" +#include "SettingUpdate.h" +#include "threads/SharedSection.h" +#include "utils/logtypes.h" + +#include <memory> +#include <set> +#include <shared_mutex> +#include <string> +#include <utility> +#include <vector> + +enum class SettingOptionsType { + Unknown = 0, + StaticTranslatable, + Static, + Dynamic +}; + +class CSetting; +using SettingPtr = std::shared_ptr<CSetting>; +using SettingConstPtr = std::shared_ptr<const CSetting>; +using SettingList = std::vector<SettingPtr>; + +/*! + \ingroup settings + \brief Setting base class containing all the properties which are common to + all settings independent of the setting type. + */ +class CSetting : public ISetting, + protected ISettingCallback, + public std::enable_shared_from_this<CSetting> +{ +public: + CSetting(const std::string& id, CSettingsManager* settingsManager = nullptr); + CSetting(const std::string& id, const CSetting& setting); + ~CSetting() override = default; + + virtual std::shared_ptr<CSetting> Clone(const std::string &id) const = 0; + void MergeBasics(const CSetting& other); + virtual void MergeDetails(const CSetting& other) = 0; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + virtual SettingType GetType() const = 0; + virtual bool FromString(const std::string &value) = 0; + virtual std::string ToString() const = 0; + virtual bool Equals(const std::string &value) const = 0; + virtual bool CheckValidity(const std::string &value) const = 0; + virtual void Reset() = 0; + + bool IsEnabled() const; + bool GetEnabled() const { return m_enabled; } + void SetEnabled(bool enabled); + bool IsDefault() const { return !m_changed; } + const std::string& GetParent() const { return m_parentSetting; } + void SetParent(const std::string& parentSetting) { m_parentSetting = parentSetting; } + SettingLevel GetLevel() const { return m_level; } + void SetLevel(SettingLevel level) { m_level = level; } + std::shared_ptr<const ISettingControl> GetControl() const { return m_control; } + std::shared_ptr<ISettingControl> GetControl() { return m_control; } + void SetControl(std::shared_ptr<ISettingControl> control) { m_control = std::move(control); } + const SettingDependencies& GetDependencies() const { return m_dependencies; } + void SetDependencies(const SettingDependencies &dependencies) { m_dependencies = dependencies; } + const std::set<CSettingUpdate>& GetUpdates() const { return m_updates; } + + void SetCallback(ISettingCallback *callback) { m_callback = callback; } + + bool IsReference() const { return !m_referencedId.empty(); } + const std::string& GetReferencedId() const { return m_referencedId; } + void SetReferencedId(const std::string& referencedId) { m_referencedId = referencedId; } + void MakeReference(const std::string& referencedId = ""); + + bool GetVisible() const { return ISetting::IsVisible(); } + // overrides of ISetting + bool IsVisible() const override; + + // implementation of ISettingCallback + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + /*! + \brief Deserializes the given XML node to retrieve a setting object's identifier and + whether the setting is a reference to another setting or not. + + \param node XML node containing a setting object's identifier + \param identification Will contain the deserialized setting object's identifier + \param isReference Whether the setting is a reference to the setting with the determined identifier + \return True if a setting object's identifier was deserialized, false otherwise + */ + static bool DeserializeIdentification(const TiXmlNode* node, + std::string& identification, + bool& isReference); + +protected: + // implementation of ISettingCallback + bool OnSettingChanging(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) override; + void OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) override; + + void Copy(const CSetting &setting); + + template<class TSetting> + std::shared_ptr<TSetting> shared_from_base() + { + return std::static_pointer_cast<TSetting>(shared_from_this()); + } + + ISettingCallback *m_callback = nullptr; + bool m_enabled = true; + std::string m_parentSetting; + SettingLevel m_level = SettingLevel::Standard; + std::shared_ptr<ISettingControl> m_control; + SettingDependencies m_dependencies; + std::set<CSettingUpdate> m_updates; + bool m_changed = false; + mutable CSharedSection m_critical; + + std::string m_referencedId; + +private: + static Logger s_logger; +}; + +template<typename TValue, SettingType TSettingType> +class CTraitedSetting : public CSetting +{ +public: + typedef TValue Value; + + // implementation of CSetting + SettingType GetType() const override { return TSettingType; } + + static SettingType Type() { return TSettingType; } + +protected: + CTraitedSetting(const std::string& id, CSettingsManager* settingsManager = nullptr) + : CSetting(id, settingsManager) + { } + CTraitedSetting(const std::string& id, const CTraitedSetting& setting) : CSetting(id, setting) {} + ~CTraitedSetting() override = default; +}; + +/*! + \ingroup settings + \brief List setting implementation + \sa CSetting + */ +class CSettingList : public CSetting +{ +public: + CSettingList(const std::string &id, std::shared_ptr<CSetting> settingDefinition, CSettingsManager *settingsManager = nullptr); + CSettingList(const std::string &id, std::shared_ptr<CSetting> settingDefinition, int label, CSettingsManager *settingsManager = nullptr); + CSettingList(const std::string &id, const CSettingList &setting); + ~CSettingList() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + SettingType GetType() const override { return SettingType::List; } + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + void Reset() override; + + SettingType GetElementType() const; + std::shared_ptr<CSetting> GetDefinition() { return m_definition; } + std::shared_ptr<const CSetting> GetDefinition() const { return m_definition; } + void SetDefinition(std::shared_ptr<CSetting> definition) { m_definition = std::move(definition); } + + const std::string& GetDelimiter() const { return m_delimiter; } + void SetDelimiter(const std::string &delimiter) { m_delimiter = delimiter; } + int GetMinimumItems() const { return m_minimumItems; } + void SetMinimumItems(int minimumItems) { m_minimumItems = minimumItems; } + int GetMaximumItems() const { return m_maximumItems; } + void SetMaximumItems(int maximumItems) { m_maximumItems = maximumItems; } + + bool FromString(const std::vector<std::string> &value); + + const SettingList& GetValue() const { return m_values; } + bool SetValue(const SettingList &values); + const SettingList& GetDefault() const { return m_defaults; } + void SetDefault(const SettingList &values); + +protected: + void copy(const CSettingList &setting); + static void copy(const SettingList &srcValues, SettingList &dstValues); + bool fromString(const std::string &strValue, SettingList &values) const; + bool fromValues(const std::vector<std::string> &strValues, SettingList &values) const; + std::string toString(const SettingList &values) const; + + SettingList m_values; + SettingList m_defaults; + std::shared_ptr<CSetting> m_definition; + std::string m_delimiter = "|"; + int m_minimumItems = 0; + int m_maximumItems = -1; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Boolean setting implementation. + \sa CSetting + */ +class CSettingBool : public CTraitedSetting<bool, SettingType::Boolean> +{ +public: + CSettingBool(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingBool(const std::string &id, const CSettingBool &setting); + CSettingBool(const std::string &id, int label, bool value, CSettingsManager *settingsManager = nullptr); + ~CSettingBool() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + void Reset() override { SetValue(m_default); } + + bool GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + bool SetValue(bool value); + bool GetDefault() const { return m_default; } + void SetDefault(bool value); + +private: + static constexpr Value DefaultValue = false; + + void copy(const CSettingBool &setting); + bool fromString(const std::string &strValue, bool &value) const; + + bool m_value = DefaultValue; + bool m_default = DefaultValue; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Integer setting implementation + \sa CSetting + */ +class CSettingInt : public CTraitedSetting<int, SettingType::Integer> +{ +public: + CSettingInt(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingInt(const std::string &id, const CSettingInt &setting); + CSettingInt(const std::string &id, int label, int value, CSettingsManager *settingsManager = nullptr); + CSettingInt(const std::string &id, int label, int value, int minimum, int step, int maximum, CSettingsManager *settingsManager = nullptr); + CSettingInt(const std::string &id, int label, int value, const TranslatableIntegerSettingOptions &options, CSettingsManager *settingsManager = nullptr); + ~CSettingInt() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + virtual bool CheckValidity(int value) const; + void Reset() override { SetValue(m_default); } + + int GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + bool SetValue(int value); + int GetDefault() const { return m_default; } + void SetDefault(int value); + + int GetMinimum() const { return m_min; } + void SetMinimum(int minimum) { m_min = minimum; } + int GetStep() const { return m_step; } + void SetStep(int step) { m_step = step; } + int GetMaximum() const { return m_max; } + void SetMaximum(int maximum) { m_max = maximum; } + + SettingOptionsType GetOptionsType() const; + const TranslatableIntegerSettingOptions& GetTranslatableOptions() const { return m_translatableOptions; } + void SetTranslatableOptions(const TranslatableIntegerSettingOptions &options) { m_translatableOptions = options; } + const IntegerSettingOptions& GetOptions() const { return m_options; } + void SetOptions(const IntegerSettingOptions &options) { m_options = options; } + const std::string& GetOptionsFillerName() const { return m_optionsFillerName; } + void SetOptionsFillerName(const std::string &optionsFillerName, void *data = nullptr) + { + m_optionsFillerName = optionsFillerName; + m_optionsFillerData = data; + } + void SetOptionsFiller(IntegerSettingOptionsFiller optionsFiller, void *data = nullptr) + { + m_optionsFiller = optionsFiller; + m_optionsFillerData = data; + } + IntegerSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + IntegerSettingOptions UpdateDynamicOptions(); + SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } + void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } + +private: + static constexpr Value DefaultValue = 0; + static constexpr Value DefaultMin = DefaultValue; + static constexpr Value DefaultStep = 1; + static constexpr Value DefaultMax = DefaultValue; + + void copy(const CSettingInt &setting); + static bool fromString(const std::string &strValue, int &value); + + int m_value = DefaultValue; + int m_default = DefaultValue; + int m_min = DefaultMin; + int m_step = DefaultStep; + int m_max = DefaultMax; + TranslatableIntegerSettingOptions m_translatableOptions; + IntegerSettingOptions m_options; + std::string m_optionsFillerName; + IntegerSettingOptionsFiller m_optionsFiller = nullptr; + void *m_optionsFillerData = nullptr; + IntegerSettingOptions m_dynamicOptions; + SettingOptionsSort m_optionsSort = SettingOptionsSort::NoSorting; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Real number setting implementation. + \sa CSetting + */ +class CSettingNumber : public CTraitedSetting<double, SettingType::Number> +{ +public: + CSettingNumber(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingNumber(const std::string &id, const CSettingNumber &setting); + CSettingNumber(const std::string &id, int label, float value, CSettingsManager *settingsManager = nullptr); + CSettingNumber(const std::string &id, int label, float value, float minimum, float step, float maximum, CSettingsManager *settingsManager = nullptr); + ~CSettingNumber() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override; + std::string ToString() const override; + bool Equals(const std::string &value) const override; + bool CheckValidity(const std::string &value) const override; + virtual bool CheckValidity(double value) const; + void Reset() override { SetValue(m_default); } + + double GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + bool SetValue(double value); + double GetDefault() const { return m_default; } + void SetDefault(double value); + + double GetMinimum() const { return m_min; } + void SetMinimum(double minimum) { m_min = minimum; } + double GetStep() const { return m_step; } + void SetStep(double step) { m_step = step; } + double GetMaximum() const { return m_max; } + void SetMaximum(double maximum) { m_max = maximum; } + +private: + static constexpr Value DefaultValue = 0.0; + static constexpr Value DefaultMin = DefaultValue; + static constexpr Value DefaultStep = 1.0; + static constexpr Value DefaultMax = DefaultValue; + + virtual void copy(const CSettingNumber &setting); + static bool fromString(const std::string &strValue, double &value); + + double m_value = DefaultValue; + double m_default = DefaultValue; + double m_min = DefaultMin; + double m_step = DefaultStep; + double m_max = DefaultMax; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief String setting implementation. + \sa CSetting + */ +class CSettingString : public CTraitedSetting<std::string, SettingType::String> +{ +public: + CSettingString(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingString(const std::string &id, const CSettingString &setting); + CSettingString(const std::string &id, int label, const std::string &value, CSettingsManager *settingsManager = nullptr); + ~CSettingString() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + bool FromString(const std::string &value) override { return SetValue(value); } + std::string ToString() const override { return m_value; } + bool Equals(const std::string &value) const override { return m_value == value; } + bool CheckValidity(const std::string &value) const override; + void Reset() override { SetValue(m_default); } + + virtual const std::string& GetValue() const + { + std::shared_lock<CSharedSection> lock(m_critical); + return m_value; + } + virtual bool SetValue(const std::string &value); + virtual const std::string& GetDefault() const { return m_default; } + virtual void SetDefault(const std::string &value); + + virtual bool AllowEmpty() const { return m_allowEmpty; } + void SetAllowEmpty(bool allowEmpty) { m_allowEmpty = allowEmpty; } + virtual bool AllowNewOption() const { return m_allowNewOption; } + void SetAllowNewOption(bool allowNewOption) { m_allowNewOption = allowNewOption; } + + SettingOptionsType GetOptionsType() const; + const TranslatableStringSettingOptions& GetTranslatableOptions() const { return m_translatableOptions; } + void SetTranslatableOptions(const TranslatableStringSettingOptions &options) { m_translatableOptions = options; } + const StringSettingOptions& GetOptions() const { return m_options; } + void SetOptions(const StringSettingOptions &options) { m_options = options; } + const std::string& GetOptionsFillerName() const { return m_optionsFillerName; } + void SetOptionsFillerName(const std::string &optionsFillerName, void *data = nullptr) + { + m_optionsFillerName = optionsFillerName; + m_optionsFillerData = data; + } + void SetOptionsFiller(StringSettingOptionsFiller optionsFiller, void *data = nullptr) + { + m_optionsFiller = optionsFiller; + m_optionsFillerData = data; + } + StringSettingOptions GetDynamicOptions() const { return m_dynamicOptions; } + StringSettingOptions UpdateDynamicOptions(); + SettingOptionsSort GetOptionsSort() const { return m_optionsSort; } + void SetOptionsSort(SettingOptionsSort optionsSort) { m_optionsSort = optionsSort; } + +protected: + static const Value DefaultValue; + + virtual void copy(const CSettingString &setting); + + std::string m_value; + std::string m_default; + bool m_allowEmpty = false; + bool m_allowNewOption = false; + TranslatableStringSettingOptions m_translatableOptions; + StringSettingOptions m_options; + std::string m_optionsFillerName; + StringSettingOptionsFiller m_optionsFiller = nullptr; + void *m_optionsFillerData = nullptr; + StringSettingOptions m_dynamicOptions; + SettingOptionsSort m_optionsSort = SettingOptionsSort::NoSorting; + + static Logger s_logger; +}; + +/*! + \ingroup settings + \brief Action setting implementation. + + A setting action will trigger a call to the OnSettingAction() callback method + when activated. + + \sa CSetting + */ +class CSettingAction : public CSetting +{ +public: + CSettingAction(const std::string &id, CSettingsManager *settingsManager = nullptr); + CSettingAction(const std::string &id, int label, CSettingsManager *settingsManager = nullptr); + CSettingAction(const std::string &id, const CSettingAction &setting); + ~CSettingAction() override = default; + + std::shared_ptr<CSetting> Clone(const std::string &id) const override; + void MergeDetails(const CSetting& other) override; + + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + SettingType GetType() const override { return SettingType::Action; } + bool FromString(const std::string &value) override { return CheckValidity(value); } + std::string ToString() const override { return ""; } + bool Equals(const std::string &value) const override { return value.empty(); } + bool CheckValidity(const std::string &value) const override { return value.empty(); } + void Reset() override { } + + bool HasData() const { return !m_data.empty(); } + const std::string& GetData() const { return m_data; } + void SetData(const std::string& data) { m_data = data; } + +protected: + virtual void copy(const CSettingAction& setting); + + std::string m_data; + + static Logger s_logger; +}; diff --git a/xbmc/settings/lib/SettingCategoryAccess.cpp b/xbmc/settings/lib/SettingCategoryAccess.cpp new file mode 100644 index 0000000..4640586 --- /dev/null +++ b/xbmc/settings/lib/SettingCategoryAccess.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingCategoryAccess.h" + +#include "SettingConditions.h" +#include "SettingsManager.h" + +bool CSettingCategoryAccessCondition::Check() const +{ + if (m_value.empty()) + return true; + + if (m_settingsManager == nullptr) + return false; + + bool found = m_settingsManager->GetConditions().Check(m_value, "true"); + if (m_negated) + return !found; + + return found; +} + +bool CSettingCategoryAccessConditionCombination::Check() const +{ + if (m_operations.empty() && m_values.empty()) + return true; + + return CSettingConditionCombination::Check(); +} + +CSettingCategoryAccess::CSettingCategoryAccess(CSettingsManager *settingsManager /* = nullptr */) + : CSettingCondition(settingsManager) +{ + m_operation = CBooleanLogicOperationPtr(new CSettingCategoryAccessConditionCombination(m_settingsManager)); +} diff --git a/xbmc/settings/lib/SettingCategoryAccess.h b/xbmc/settings/lib/SettingCategoryAccess.h new file mode 100644 index 0000000..5d4ceb8 --- /dev/null +++ b/xbmc/settings/lib/SettingCategoryAccess.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingConditions.h" + +#include <set> +#include <string> + +class CSettingCategoryAccessCondition : public CSettingConditionItem +{ +public: + explicit CSettingCategoryAccessCondition(CSettingsManager *settingsManager = nullptr) + : CSettingConditionItem(settingsManager) + { } + ~CSettingCategoryAccessCondition() override = default; + + bool Check() const override; +}; + +class CSettingCategoryAccessConditionCombination : public CSettingConditionCombination +{ +public: + explicit CSettingCategoryAccessConditionCombination(CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { } + ~CSettingCategoryAccessConditionCombination() override = default; + + bool Check() const override; + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingCategoryAccessConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingCategoryAccessCondition(m_settingsManager); } +}; + +class CSettingCategoryAccess : public CSettingCondition +{ +public: + explicit CSettingCategoryAccess(CSettingsManager *settingsManager = nullptr); + ~CSettingCategoryAccess() override = default; +}; diff --git a/xbmc/settings/lib/SettingConditions.cpp b/xbmc/settings/lib/SettingConditions.cpp new file mode 100644 index 0000000..7146834 --- /dev/null +++ b/xbmc/settings/lib/SettingConditions.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingConditions.h" + +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" + +bool CSettingConditionItem::Deserialize(const TiXmlNode *node) +{ + if (!CBooleanLogicValue::Deserialize(node)) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + // get the "name" attribute + auto strAttribute = elem->Attribute(SETTING_XML_ATTR_NAME); + if (strAttribute != nullptr) + m_name = strAttribute; + + // get the "setting" attribute + strAttribute = elem->Attribute(SETTING_XML_ATTR_SETTING); + if (strAttribute != nullptr) + m_setting = strAttribute; + + return true; +} + +bool CSettingConditionItem::Check() const +{ + if (m_settingsManager == nullptr) + return false; + + return m_settingsManager->GetConditions().Check(m_name, m_value, m_settingsManager->GetSetting(m_setting)) == !m_negated; +} + +bool CSettingConditionCombination::Check() const +{ + bool ok = false; + for (const auto& operation : m_operations) + { + if (operation == nullptr) + continue; + + const auto combination = std::static_pointer_cast<const CSettingConditionCombination>(operation); + if (combination == nullptr) + continue; + + if (combination->Check()) + ok = true; + else if (m_operation == BooleanLogicOperationAnd) + return false; + } + + for (const auto& value : m_values) + { + if (value == nullptr) + continue; + + const auto condition = std::static_pointer_cast<const CSettingConditionItem>(value); + if (condition == nullptr) + continue; + + if (condition->Check()) + ok = true; + else if (m_operation == BooleanLogicOperationAnd) + return false; + } + + return ok; +} + +CSettingCondition::CSettingCondition(CSettingsManager *settingsManager /* = nullptr */) + : ISettingCondition(settingsManager) +{ + m_operation = CBooleanLogicOperationPtr(new CSettingConditionCombination(settingsManager)); +} + +bool CSettingCondition::Check() const +{ + auto combination = std::static_pointer_cast<CSettingConditionCombination>(m_operation); + if (combination == nullptr) + return false; + + return combination->Check(); +} + +void CSettingConditionsManager::AddCondition(std::string condition) +{ + if (condition.empty()) + return; + + StringUtils::ToLower(condition); + + m_defines.insert(condition); +} + +void CSettingConditionsManager::AddDynamicCondition(std::string identifier, SettingConditionCheck condition, void *data /*= nullptr*/) +{ + if (identifier.empty() || condition == nullptr) + return; + + StringUtils::ToLower(identifier); + + m_conditions.emplace(identifier, std::make_pair(condition, data)); +} + +void CSettingConditionsManager::RemoveDynamicCondition(std::string identifier) +{ + if (identifier.empty()) + return; + + StringUtils::ToLower(identifier); + + auto it = m_conditions.find(identifier); + if (it != m_conditions.end()) + m_conditions.erase(it); +} + +bool CSettingConditionsManager::Check( + std::string condition, + const std::string& value /* = "" */, + const std::shared_ptr<const CSetting>& setting /* = nullptr */) const +{ + if (condition.empty()) + return false; + + StringUtils::ToLower(condition); + + // special handling of "isdefined" conditions + if (condition == "isdefined") + { + std::string tmpValue = value; + StringUtils::ToLower(tmpValue); + + return m_defines.find(tmpValue) != m_defines.end(); + } + + auto conditionIt = m_conditions.find(condition); + if (conditionIt == m_conditions.end()) + return false; + + return conditionIt->second.first(condition, value, setting, conditionIt->second.second); +} diff --git a/xbmc/settings/lib/SettingConditions.h b/xbmc/settings/lib/SettingConditions.h new file mode 100644 index 0000000..aee2c06 --- /dev/null +++ b/xbmc/settings/lib/SettingConditions.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingDefinitions.h" +#include "utils/BooleanLogic.h" + +#include <map> +#include <set> +#include <string> +#include <utility> + +class CSettingsManager; +class CSetting; + +using SettingConditionCheck = bool (*)(const std::string& condition, + const std::string& value, + const std::shared_ptr<const CSetting>& setting, + void* data); + +class ISettingCondition +{ +public: + explicit ISettingCondition(CSettingsManager *settingsManager) + : m_settingsManager(settingsManager) + { } + virtual ~ISettingCondition() = default; + + virtual bool Check() const = 0; + +protected: + CSettingsManager *m_settingsManager; +}; + +class CSettingConditionItem : public CBooleanLogicValue, public ISettingCondition +{ +public: + explicit CSettingConditionItem(CSettingsManager *settingsManager = nullptr) + : ISettingCondition(settingsManager) + { } + ~CSettingConditionItem() override = default; + + bool Deserialize(const TiXmlNode *node) override; + const char* GetTag() const override { return SETTING_XML_ELM_CONDITION; } + bool Check() const override; + +protected: + std::string m_name; + std::string m_setting; +}; + +class CSettingConditionCombination : public CBooleanLogicOperation, public ISettingCondition +{ +public: + explicit CSettingConditionCombination(CSettingsManager *settingsManager = nullptr) + : ISettingCondition(settingsManager) + { } + ~CSettingConditionCombination() override = default; + + bool Check() const override; + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingConditionItem(m_settingsManager); } +}; + +class CSettingCondition : public CBooleanLogic, public ISettingCondition +{ +public: + explicit CSettingCondition(CSettingsManager *settingsManager = nullptr); + ~CSettingCondition() override = default; + + bool Check() const override; +}; + +class CSettingConditionsManager +{ +public: + CSettingConditionsManager() = default; + CSettingConditionsManager(const CSettingConditionsManager&) = delete; + CSettingConditionsManager const& operator=(CSettingConditionsManager const&) = delete; + virtual ~CSettingConditionsManager() = default; + + void AddCondition(std::string condition); + void AddDynamicCondition(std::string identifier, SettingConditionCheck condition, void *data = nullptr); + void RemoveDynamicCondition(std::string identifier); + + bool Check( + std::string condition, + const std::string& value = "", + const std::shared_ptr<const CSetting>& setting = std::shared_ptr<const CSetting>()) const; + +private: + using SettingConditionPair = std::pair<std::string, std::pair<SettingConditionCheck, void*>>; + using SettingConditionMap = std::map<std::string, std::pair<SettingConditionCheck, void*>>; + + SettingConditionMap m_conditions; + std::set<std::string> m_defines; +}; diff --git a/xbmc/settings/lib/SettingDefinitions.h b/xbmc/settings/lib/SettingDefinitions.h new file mode 100644 index 0000000..ae18347 --- /dev/null +++ b/xbmc/settings/lib/SettingDefinitions.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Variant.h" + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#define SETTING_XML_ROOT "settings" +#define SETTING_XML_ROOT_VERSION "version" + +#define SETTING_XML_ELM_SECTION "section" +#define SETTING_XML_ELM_CATEGORY "category" +#define SETTING_XML_ELM_GROUP "group" +#define SETTING_XML_ELM_SETTING "setting" +#define SETTING_XML_ELM_VISIBLE "visible" +#define SETTING_XML_ELM_REQUIREMENT "requirement" +#define SETTING_XML_ELM_CONDITION "condition" +#define SETTING_XML_ELM_ENABLED "enable" +#define SETTING_XML_ELM_LEVEL "level" +#define SETTING_XML_ELM_DEFAULT "default" +#define SETTING_XML_ELM_VALUE "value" +#define SETTING_XML_ELM_CONTROL "control" +#define SETTING_XML_ELM_CONSTRAINTS "constraints" +#define SETTING_XML_ELM_OPTIONS "options" +#define SETTING_XML_ELM_OPTION "option" +#define SETTING_XML_ELM_MINIMUM "minimum" +#define SETTING_XML_ELM_STEP "step" +#define SETTING_XML_ELM_MAXIMUM "maximum" +#define SETTING_XML_ELM_ALLOWEMPTY "allowempty" +#define SETTING_XML_ELM_ALLOWNEWOPTION "allownewoption" +#define SETTING_XML_ELM_DEPENDENCIES "dependencies" +#define SETTING_XML_ELM_DEPENDENCY "dependency" +#define SETTING_XML_ELM_UPDATES "updates" +#define SETTING_XML_ELM_UPDATE "update" +#define SETTING_XML_ELM_ACCESS "access" +#define SETTING_XML_ELM_DELIMITER "delimiter" +#define SETTING_XML_ELM_MINIMUM_ITEMS "minimumitems" +#define SETTING_XML_ELM_MAXIMUM_ITEMS "maximumitems" +#define SETTING_XML_ELM_DATA "data" + +#define SETTING_XML_ATTR_ID "id" +#define SETTING_XML_ATTR_REFERENCE "ref" +#define SETTING_XML_ATTR_LABEL "label" +#define SETTING_XML_ATTR_HELP "help" +#define SETTING_XML_ATTR_TYPE "type" +#define SETTING_XML_ATTR_PARENT "parent" +#define SETTING_XML_ATTR_FORMAT "format" +#define SETTING_XML_ATTR_DELAYED "delayed" +#define SETTING_XML_ATTR_ON "on" +#define SETTING_XML_ATTR_OPERATOR "operator" +#define SETTING_XML_ATTR_NAME "name" +#define SETTING_XML_ATTR_SETTING "setting" +#define SETTING_XML_ATTR_BEFORE "before" +#define SETTING_XML_ATTR_AFTER "after" + +struct IntegerSettingOption +{ + IntegerSettingOption(const std::string& _label, int _value) + : label(_label), value(_value) {} + + IntegerSettingOption(const std::string& _label, + const std::string& _label2, + int _value, + const std::vector<std::pair<std::string, CVariant>>& props) + : label(_label), label2(_label2), value(_value), properties(props) + { + } + + std::string label; + std::string label2; + int value = 0; + std::vector<std::pair<std::string, CVariant>> properties; +}; + +struct StringSettingOption +{ + StringSettingOption(const std::string& _label, const std::string& _value) + : label(_label), value(_value) {} + + StringSettingOption(const std::string& _label, + const std::string& _label2, + const std::string& _value, + const std::vector<std::pair<std::string, CVariant>>& props) + : label(_label), label2(_label2), value(_value), properties(props) + { + } + + std::string label; + std::string label2; + std::string value; + std::vector<std::pair<std::string, CVariant>> properties; +}; + +struct TranslatableIntegerSettingOption +{ + TranslatableIntegerSettingOption() = default; + TranslatableIntegerSettingOption(int _label, int _value, const std::string& _addonId = "") + : label(_label), value(_value), addonId(_addonId) + { + } + + int label = 0; + int value = 0; + std::string addonId; // Leaved empty for Kodi labels +}; + +using TranslatableIntegerSettingOptions = std::vector<TranslatableIntegerSettingOption>; +using IntegerSettingOptions = std::vector<IntegerSettingOption>; +using TranslatableStringSettingOption = std::pair<int, std::string>; +using TranslatableStringSettingOptions = std::vector<TranslatableStringSettingOption>; +using StringSettingOptions = std::vector<StringSettingOption>; + +class CSetting; +using IntegerSettingOptionsFiller = void (*)(const std::shared_ptr<const CSetting>& setting, + IntegerSettingOptions& list, + int& current, + void* data); +using StringSettingOptionsFiller = void (*)(const std::shared_ptr<const CSetting>& setting, + StringSettingOptions& list, + std::string& current, + void* data); + +enum class SettingOptionsSort +{ + NoSorting, + Ascending, + Descending +}; diff --git a/xbmc/settings/lib/SettingDependency.cpp b/xbmc/settings/lib/SettingDependency.cpp new file mode 100644 index 0000000..e3122da --- /dev/null +++ b/xbmc/settings/lib/SettingDependency.cpp @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingDependency.h" + +#include "ServiceBroker.h" +#include "Setting.h" +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <memory> +#include <set> +#include <stdlib.h> +#include <string> + +Logger CSettingDependencyCondition::s_logger; + +CSettingDependencyCondition::CSettingDependencyCondition( + CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependencyCondition(settingsManager, "", "", "") +{ +} + +CSettingDependencyCondition::CSettingDependencyCondition( + const std::string& setting, + const std::string& value, + SettingDependencyOperator op, + bool negated /* = false */, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependencyCondition( + settingsManager, setting, setting, value, SettingDependencyTarget::Setting, op, negated) +{ +} + +CSettingDependencyCondition::CSettingDependencyCondition( + const std::string& strProperty, + const std::string& value, + const std::string& setting /* = "" */, + bool negated /* = false */, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependencyCondition(settingsManager, + strProperty, + setting, + value, + SettingDependencyTarget::Property, + SettingDependencyOperator::Equals, + negated) +{ +} + +CSettingDependencyCondition::CSettingDependencyCondition( + CSettingsManager* settingsManager, + const std::string& strProperty, + const std::string& setting, + const std::string& value, + SettingDependencyTarget target /* = SettingDependencyTarget::Unknown */, + SettingDependencyOperator op /* = SettingDependencyOperator::Equals */, + bool negated /* = false */) + : CSettingConditionItem(settingsManager), m_target(target), m_operator(op) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingDependencyCondition"); + + m_name = strProperty; + m_setting = setting; + m_value = value; + m_negated = negated; +} + +bool CSettingDependencyCondition::Deserialize(const TiXmlNode *node) +{ + if (!CSettingConditionItem::Deserialize(node)) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + m_target = SettingDependencyTarget::Setting; + auto strTarget = elem->Attribute(SETTING_XML_ATTR_ON); + if (strTarget != nullptr && !setTarget(strTarget)) + { + s_logger->warn("unknown target \"{}\"", strTarget); + return false; + } + + if (m_target != SettingDependencyTarget::Setting && m_name.empty()) + { + s_logger->warn("missing name for dependency"); + return false; + } + + if (m_target == SettingDependencyTarget::Setting) + { + if (m_setting.empty()) + { + s_logger->warn("missing setting for dependency"); + return false; + } + + m_name = m_setting; + } + + m_operator = SettingDependencyOperator::Equals; + auto strOperator = elem->Attribute(SETTING_XML_ATTR_OPERATOR); + if (strOperator != nullptr && !setOperator(strOperator)) + { + s_logger->warn("unknown operator \"{}\"", strOperator); + return false; + } + + return true; +} + +bool CSettingDependencyCondition::Check() const +{ + if (m_name.empty() || + m_target == SettingDependencyTarget::Unknown || + m_operator == SettingDependencyOperator::Unknown || + m_settingsManager == nullptr) + return false; + + bool result = false; + switch (m_target) + { + case SettingDependencyTarget::Setting: + { + if (m_setting.empty()) + return false; + + auto setting = m_settingsManager->GetSetting(m_setting); + if (setting == nullptr) + { + s_logger->warn("unable to check condition on unknown setting \"{}\"", m_setting); + return false; + } + + switch (m_operator) + { + case SettingDependencyOperator::Equals: + result = setting->Equals(m_value); + break; + + case SettingDependencyOperator::LessThan: + { + const auto value = setting->ToString(); + if (StringUtils::IsInteger(m_value)) + result = strtol(value.c_str(), nullptr, 0) < strtol(m_value.c_str(), nullptr, 0); + else + result = value.compare(m_value) < 0; + break; + } + + case SettingDependencyOperator::GreaterThan: + { + const auto value = setting->ToString(); + if (StringUtils::IsInteger(m_value)) + result = strtol(value.c_str(), nullptr, 0) > strtol(m_value.c_str(), nullptr, 0); + else + result = value.compare(m_value) > 0; + break; + } + + case SettingDependencyOperator::Contains: + result = (setting->ToString().find(m_value) != std::string::npos); + break; + + case SettingDependencyOperator::Unknown: + default: + break; + } + + break; + } + + case SettingDependencyTarget::Property: + { + SettingConstPtr setting; + if (!m_setting.empty()) + { + setting = m_settingsManager->GetSetting(m_setting); + if (setting == nullptr) + { + s_logger->warn("unable to check condition on unknown setting \"{}\"", m_setting); + return false; + } + } + result = m_settingsManager->GetConditions().Check(m_name, m_value, setting); + break; + } + + default: + return false; + } + + return result == !m_negated; +} + +bool CSettingDependencyCondition::setTarget(const std::string &target) +{ + if (StringUtils::EqualsNoCase(target, "setting")) + m_target = SettingDependencyTarget::Setting; + else if (StringUtils::EqualsNoCase(target, "property")) + m_target = SettingDependencyTarget::Property; + else + return false; + + return true; +} + +bool CSettingDependencyCondition::setOperator(const std::string &op) +{ + size_t length = 0; + if (StringUtils::EndsWithNoCase(op, "is")) + { + m_operator = SettingDependencyOperator::Equals; + length = 2; + } + else if (StringUtils::EndsWithNoCase(op, "lessthan")) + { + m_operator = SettingDependencyOperator::LessThan; + length = 8; + } + else if (StringUtils::EndsWithNoCase(op, "lt")) + { + m_operator = SettingDependencyOperator::LessThan; + length = 2; + } + else if (StringUtils::EndsWithNoCase(op, "greaterthan")) + { + m_operator = SettingDependencyOperator::GreaterThan; + length = 11; + } + else if (StringUtils::EndsWithNoCase(op, "gt")) + { + m_operator = SettingDependencyOperator::GreaterThan; + length = 2; + } + else if (StringUtils::EndsWithNoCase(op, "contains")) + { + m_operator = SettingDependencyOperator::Contains; + length = 8; + } + + if (op.size() > length + 1) + return false; + if (op.size() == length + 1) + { + if (!StringUtils::StartsWith(op, "!")) + return false; + m_negated = true; + } + + return true; +} + +bool CSettingDependencyConditionCombination::Deserialize(const TiXmlNode *node) +{ + if (node == nullptr) + return false; + + size_t numOperations = m_operations.size(); + size_t numValues = m_values.size(); + + if (!CSettingConditionCombination::Deserialize(node)) + return false; + + if (numOperations < m_operations.size()) + { + for (size_t i = numOperations; i < m_operations.size(); i++) + { + if (m_operations[i] == nullptr) + continue; + + auto combination = static_cast<CSettingDependencyConditionCombination*>(m_operations[i].get()); + if (combination == nullptr) + continue; + + const std::set<std::string>& settings = combination->GetSettings(); + m_settings.insert(settings.begin(), settings.end()); + } + } + + if (numValues < m_values.size()) + { + for (size_t i = numValues; i < m_values.size(); i++) + { + if (m_values[i] == nullptr) + continue; + + auto condition = static_cast<CSettingDependencyCondition*>(m_values[i].get()); + if (condition == nullptr) + continue; + + auto settingId = condition->GetSetting(); + if (!settingId.empty()) + m_settings.insert(settingId); + } + } + + return true; +} + +CSettingDependencyConditionCombination* CSettingDependencyConditionCombination::Add( + const CSettingDependencyConditionPtr& condition) +{ + if (condition != nullptr) + { + m_values.push_back(condition); + + auto settingId = condition->GetSetting(); + if (!settingId.empty()) + m_settings.insert(settingId); + } + + return this; +} + +CSettingDependencyConditionCombination* CSettingDependencyConditionCombination::Add( + const CSettingDependencyConditionCombinationPtr& operation) +{ + if (operation != nullptr) + { + m_operations.push_back(operation); + + const auto& settings = operation->GetSettings(); + m_settings.insert(settings.begin(), settings.end()); + } + + return this; +} + +Logger CSettingDependency::s_logger; + +CSettingDependency::CSettingDependency(CSettingsManager* settingsManager /* = nullptr */) + : CSettingDependency(SettingDependencyType::Unknown, settingsManager) +{ +} + +CSettingDependency::CSettingDependency(SettingDependencyType type, + CSettingsManager* settingsManager /* = nullptr */) + : CSettingCondition(settingsManager), m_type(type) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingDependency"); + + m_operation = CBooleanLogicOperationPtr(new CSettingDependencyConditionCombination(m_settingsManager)); +} + +bool CSettingDependency::Deserialize(const TiXmlNode *node) +{ + if (node == nullptr) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + auto strType = elem->Attribute(SETTING_XML_ATTR_TYPE); + if (strType == nullptr || strlen(strType) <= 0 || !setType(strType)) + { + s_logger->warn("missing or unknown dependency type definition"); + return false; + } + + return CSettingCondition::Deserialize(node); +} + +std::set<std::string> CSettingDependency::GetSettings() const +{ + if (m_operation == nullptr) + return std::set<std::string>(); + + auto combination = static_cast<CSettingDependencyConditionCombination*>(m_operation.get()); + if (combination == nullptr) + return std::set<std::string>(); + + return combination->GetSettings(); +} + +CSettingDependencyConditionCombinationPtr CSettingDependency::And() +{ + if (m_operation == nullptr) + m_operation = CBooleanLogicOperationPtr(new CSettingDependencyConditionCombination(m_settingsManager)); + + m_operation->SetOperation(BooleanLogicOperationAnd); + + return std::dynamic_pointer_cast<CSettingDependencyConditionCombination>(m_operation); +} + +CSettingDependencyConditionCombinationPtr CSettingDependency::Or() +{ + if (m_operation == nullptr) + m_operation = CBooleanLogicOperationPtr(new CSettingDependencyConditionCombination(m_settingsManager)); + + m_operation->SetOperation(BooleanLogicOperationOr); + + return std::dynamic_pointer_cast<CSettingDependencyConditionCombination>(m_operation); +} + +bool CSettingDependency::setType(const std::string &type) +{ + if (StringUtils::EqualsNoCase(type, "enable")) + m_type = SettingDependencyType::Enable; + else if (StringUtils::EqualsNoCase(type, "update")) + m_type = SettingDependencyType::Update; + else if (StringUtils::EqualsNoCase(type, "visible")) + m_type = SettingDependencyType::Visible; + else + return false; + + return true; +} diff --git a/xbmc/settings/lib/SettingDependency.h b/xbmc/settings/lib/SettingDependency.h new file mode 100644 index 0000000..5cb34db --- /dev/null +++ b/xbmc/settings/lib/SettingDependency.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingConditions.h" +#include "utils/BooleanLogic.h" +#include "utils/logtypes.h" + +#include <list> +#include <set> +#include <string> + +enum class SettingDependencyType { + Unknown = 0, + Enable, + Update, + Visible +}; + +enum class SettingDependencyOperator { + Unknown = 0, + Equals, + LessThan, + GreaterThan, + Contains +}; + +enum class SettingDependencyTarget { + Unknown = 0, + Setting, + Property +}; + +class CSettingDependencyCondition : public CSettingConditionItem +{ +public: + explicit CSettingDependencyCondition(CSettingsManager *settingsManager = nullptr); + CSettingDependencyCondition(const std::string &setting, const std::string &value, + SettingDependencyOperator op, bool negated = false, + CSettingsManager *settingsManager = nullptr); + CSettingDependencyCondition(const std::string &strProperty, const std::string &value, + const std::string &setting = "", bool negated = false, + CSettingsManager *settingsManager = nullptr); + ~CSettingDependencyCondition() override = default; + + bool Deserialize(const TiXmlNode *node) override; + bool Check() const override; + + const std::string& GetName() const { return m_name; } + const std::string& GetSetting() const { return m_setting; } + SettingDependencyTarget GetTarget() const { return m_target; } + SettingDependencyOperator GetOperator() const { return m_operator; } + +private: + CSettingDependencyCondition(CSettingsManager* settingsManager, + const std::string& strProperty, + const std::string& setting, + const std::string& value, + SettingDependencyTarget target = SettingDependencyTarget::Unknown, + SettingDependencyOperator op = SettingDependencyOperator::Equals, + bool negated = false); + + bool setTarget(const std::string &target); + bool setOperator(const std::string &op); + + SettingDependencyTarget m_target = SettingDependencyTarget::Unknown; + SettingDependencyOperator m_operator = SettingDependencyOperator::Equals; + + static Logger s_logger; +}; + +using CSettingDependencyConditionPtr = std::shared_ptr<CSettingDependencyCondition>; + +class CSettingDependencyConditionCombination; +using CSettingDependencyConditionCombinationPtr = std::shared_ptr<CSettingDependencyConditionCombination>; + +class CSettingDependencyConditionCombination : public CSettingConditionCombination +{ +public: + explicit CSettingDependencyConditionCombination(CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { } + CSettingDependencyConditionCombination(BooleanLogicOperation op, CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { + SetOperation(op); + } + ~CSettingDependencyConditionCombination() override = default; + + bool Deserialize(const TiXmlNode *node) override; + + const std::set<std::string>& GetSettings() const { return m_settings; } + + CSettingDependencyConditionCombination* Add(const CSettingDependencyConditionPtr& condition); + CSettingDependencyConditionCombination* Add( + const CSettingDependencyConditionCombinationPtr& operation); + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingDependencyConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingDependencyCondition(m_settingsManager); } + + std::set<std::string> m_settings; +}; + +class CSettingDependency : public CSettingCondition +{ +public: + explicit CSettingDependency(CSettingsManager *settingsManager = nullptr); + CSettingDependency(SettingDependencyType type, CSettingsManager *settingsManager = nullptr); + ~CSettingDependency() override = default; + + bool Deserialize(const TiXmlNode *node) override; + + SettingDependencyType GetType() const { return m_type; } + std::set<std::string> GetSettings() const; + + CSettingDependencyConditionCombinationPtr And(); + CSettingDependencyConditionCombinationPtr Or(); + +private: + bool setType(const std::string &type); + + SettingDependencyType m_type = SettingDependencyType::Unknown; + + static Logger s_logger; +}; + +using SettingDependencies = std::list<CSettingDependency>; +using SettingDependencyMap = std::map<std::string, SettingDependencies>; diff --git a/xbmc/settings/lib/SettingLevel.h b/xbmc/settings/lib/SettingLevel.h new file mode 100644 index 0000000..db8e2b5 --- /dev/null +++ b/xbmc/settings/lib/SettingLevel.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! + \ingroup settings + \brief Levels which every setting is assigned to. + */ +enum class SettingLevel { + Basic = 0, + Standard, + Advanced, + Expert, + Internal +}; diff --git a/xbmc/settings/lib/SettingRequirement.cpp b/xbmc/settings/lib/SettingRequirement.cpp new file mode 100644 index 0000000..5ba81ab --- /dev/null +++ b/xbmc/settings/lib/SettingRequirement.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingRequirement.h" + +#include "SettingsManager.h" + +bool CSettingRequirementCondition::Check() const +{ + if (m_settingsManager == nullptr) + return false; + + bool found = m_settingsManager->GetConditions().Check("IsDefined", m_value); + if (m_negated) + return !found; + + return found; +} + +bool CSettingRequirementConditionCombination::Check() const +{ + if (m_operations.empty() && m_values.empty()) + return true; + + return CSettingConditionCombination::Check(); +} + +CSettingRequirement::CSettingRequirement(CSettingsManager *settingsManager /* = nullptr */) + : CSettingCondition(settingsManager) +{ + m_operation = CBooleanLogicOperationPtr(new CSettingRequirementConditionCombination(m_settingsManager)); +} diff --git a/xbmc/settings/lib/SettingRequirement.h b/xbmc/settings/lib/SettingRequirement.h new file mode 100644 index 0000000..f8357a5 --- /dev/null +++ b/xbmc/settings/lib/SettingRequirement.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "SettingConditions.h" + +#include <set> +#include <string> + +class CSettingRequirementCondition : public CSettingConditionItem +{ +public: + explicit CSettingRequirementCondition(CSettingsManager *settingsManager = nullptr) + : CSettingConditionItem(settingsManager) + { } + ~CSettingRequirementCondition() override = default; + + bool Check() const override; +}; + +class CSettingRequirementConditionCombination : public CSettingConditionCombination +{ +public: + explicit CSettingRequirementConditionCombination(CSettingsManager *settingsManager = nullptr) + : CSettingConditionCombination(settingsManager) + { } + ~CSettingRequirementConditionCombination() override = default; + + bool Check() const override; + +private: + CBooleanLogicOperation* newOperation() override { return new CSettingRequirementConditionCombination(m_settingsManager); } + CBooleanLogicValue* newValue() override { return new CSettingRequirementCondition(m_settingsManager); } +}; + +class CSettingRequirement : public CSettingCondition +{ +public: + explicit CSettingRequirement(CSettingsManager *settingsManager = nullptr); + ~CSettingRequirement() override = default; +}; diff --git a/xbmc/settings/lib/SettingSection.cpp b/xbmc/settings/lib/SettingSection.cpp new file mode 100644 index 0000000..4f1b564 --- /dev/null +++ b/xbmc/settings/lib/SettingSection.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingSection.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "SettingsManager.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> + +template<class T> +void addISetting(const TiXmlNode* node, const T& item, std::vector<T>& items, bool toBegin = false) +{ + if (node != nullptr) + { + auto element = node->ToElement(); + if (element != nullptr) + { + // check if there is a "before" or "after" attribute to place the setting at a specific position + int position = -1; // -1 => end, 0 => before, 1 => after + auto positionId = element->Attribute(SETTING_XML_ATTR_BEFORE); + if (positionId != nullptr && strlen(positionId) > 0) + position = 0; + else if ((positionId = element->Attribute(SETTING_XML_ATTR_AFTER)) != nullptr && strlen(positionId) > 0) + position = 1; + + if (positionId != nullptr && strlen(positionId) > 0 && position >= 0) + { + for (typename std::vector<T>::iterator it = items.begin(); it != items.end(); ++it) + { + if (!StringUtils::EqualsNoCase((*it)->GetId(), positionId)) + continue; + + typename std::vector<T>::iterator positionIt = it; + if (position == 1) + ++positionIt; + + items.insert(positionIt, item); + return; + } + } + } + } + + if (!toBegin) + items.emplace_back(item); + else + items.insert(items.begin(), item); +} + +Logger CSettingGroup::s_logger; + +CSettingGroup::CSettingGroup(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingGroup"); +} + +bool CSettingGroup::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto controlElement = node->FirstChildElement(SETTING_XML_ELM_CONTROL); + if (controlElement != nullptr) + { + auto controlType = controlElement->Attribute(SETTING_XML_ATTR_TYPE); + if (controlType == nullptr || strlen(controlType) <= 0) + { + s_logger->error("unable to read control type"); + return false; + } + + m_control = m_settingsManager->CreateControl(controlType); + if (m_control == nullptr) + { + s_logger->error("unable to create new control \"{}\"", controlType); + return false; + } + + if (!m_control->Deserialize(controlElement)) + { + s_logger->warn("unable to read control \"{}\"", controlType); + m_control.reset(); + } + } + + auto settingElement = node->FirstChildElement(SETTING_XML_ELM_SETTING); + while (settingElement != nullptr) + { + std::string settingId; + bool isReference; + if (CSetting::DeserializeIdentification(settingElement, settingId, isReference)) + { + auto settingIt = std::find_if(m_settings.begin(), m_settings.end(), + [&settingId](const SettingPtr& setting) + { + return setting->GetId() == settingId; + }); + + SettingPtr setting; + if (settingIt != m_settings.end()) + setting = *settingIt; + + update = (setting != nullptr); + if (!update) + { + auto settingType = settingElement->Attribute(SETTING_XML_ATTR_TYPE); + if (settingType == nullptr || strlen(settingType) <= 0) + { + s_logger->error("unable to read setting type of \"{}\"", settingId); + return false; + } + + setting = m_settingsManager->CreateSetting(settingType, settingId, m_settingsManager); + if (setting == nullptr) + s_logger->error("unknown setting type \"{}\" of \"{}\"", settingType, settingId); + } + + if (setting == nullptr) + s_logger->error("unable to create new setting \"{}\"", settingId); + else + { + if (!setting->Deserialize(settingElement, update)) + s_logger->warn("unable to read setting \"{}\"", settingId); + else + { + // if the setting is a reference turn it into one + if (isReference) + setting->MakeReference(); + + if (!update) + addISetting(settingElement, setting, m_settings); + } + } + } + + settingElement = settingElement->NextSiblingElement(SETTING_XML_ELM_SETTING); + } + + return true; +} + +SettingList CSettingGroup::GetSettings(SettingLevel level) const +{ + SettingList settings; + for (const auto& setting : m_settings) + { + if (setting->GetLevel() <= level && setting->MeetsRequirements()) + settings.push_back(setting); + } + + return settings; +} + +void CSettingGroup::AddSetting(const SettingPtr& setting) +{ + addISetting(nullptr, setting, m_settings); +} + +void CSettingGroup::AddSettings(const SettingList &settings) +{ + for (const auto& setting : settings) + addISetting(nullptr, setting, m_settings); +} + +bool CSettingGroup::ReplaceSetting(const std::shared_ptr<const CSetting>& currentSetting, + const std::shared_ptr<CSetting>& newSetting) +{ + for (auto itSetting = m_settings.begin(); itSetting != m_settings.end(); ++itSetting) + { + if (*itSetting == currentSetting) + { + if (newSetting == nullptr) + m_settings.erase(itSetting); + else + *itSetting = newSetting; + + return true; + } + } + + return false; +} + +Logger CSettingCategory::s_logger; + +CSettingCategory::CSettingCategory(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager), + m_accessCondition(settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingCategory"); +} + +bool CSettingCategory::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto accessNode = node->FirstChild(SETTING_XML_ELM_ACCESS); + if (accessNode != nullptr && !m_accessCondition.Deserialize(accessNode)) + return false; + + auto groupNode = node->FirstChild(SETTING_XML_ELM_GROUP); + while (groupNode != nullptr) + { + std::string groupId; + if (CSettingGroup::DeserializeIdentification(groupNode, groupId)) + { + auto groupIt = std::find_if(m_groups.begin(), m_groups.end(), + [&groupId](const SettingGroupPtr& group) + { + return group->GetId() == groupId; + }); + + SettingGroupPtr group; + if (groupIt != m_groups.end()) + group = *groupIt; + + update = (group != nullptr); + if (!update) + group = std::make_shared<CSettingGroup>(groupId, m_settingsManager); + + if (group->Deserialize(groupNode, update)) + { + if (!update) + addISetting(groupNode, group, m_groups); + } + else + s_logger->warn("unable to read group \"{}\"", groupId); + } + + groupNode = groupNode->NextSibling(SETTING_XML_ELM_GROUP); + } + + return true; +} + +SettingGroupList CSettingCategory::GetGroups(SettingLevel level) const +{ + SettingGroupList groups; + for (const auto& group : m_groups) + { + if (group->MeetsRequirements() && group->IsVisible() && group->GetSettings(level).size() > 0) + groups.push_back(group); + } + + return groups; +} + +bool CSettingCategory::CanAccess() const +{ + return m_accessCondition.Check(); +} + +void CSettingCategory::AddGroup(const SettingGroupPtr& group) +{ + addISetting(nullptr, group, m_groups, false); +} + +void CSettingCategory::AddGroupToFront(const SettingGroupPtr& group) +{ + addISetting(nullptr, group, m_groups, true); +} + +void CSettingCategory::AddGroups(const SettingGroupList &groups) +{ + for (const auto& group : groups) + addISetting(nullptr, group, m_groups); +} + +Logger CSettingSection::s_logger; + +CSettingSection::CSettingSection(const std::string& id, + CSettingsManager* settingsManager /* = nullptr */) + : ISetting(id, settingsManager) +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingSection"); +} + +bool CSettingSection::Deserialize(const TiXmlNode *node, bool update /* = false */) +{ + // handle <visible> conditions + if (!ISetting::Deserialize(node, update)) + return false; + + auto categoryNode = node->FirstChild(SETTING_XML_ELM_CATEGORY); + while (categoryNode != nullptr) + { + std::string categoryId; + if (CSettingCategory::DeserializeIdentification(categoryNode, categoryId)) + { + auto categoryIt = std::find_if(m_categories.begin(), m_categories.end(), + [&categoryId](const SettingCategoryPtr& category) + { + return category->GetId() == categoryId; + }); + + SettingCategoryPtr category; + if (categoryIt != m_categories.end()) + category = *categoryIt; + + update = (category != nullptr); + if (!update) + category = std::make_shared<CSettingCategory>(categoryId, m_settingsManager); + + if (category->Deserialize(categoryNode, update)) + { + if (!update) + addISetting(categoryNode, category, m_categories); + } + else + s_logger->warn("unable to read category \"{}\"", categoryId); + } + + categoryNode = categoryNode->NextSibling(SETTING_XML_ELM_CATEGORY); + } + + return true; +} + +SettingCategoryList CSettingSection::GetCategories(SettingLevel level) const +{ + SettingCategoryList categories; + for (const auto& category : m_categories) + { + if (category->MeetsRequirements() && category->IsVisible() && category->GetGroups(level).size() > 0) + categories.push_back(category); + } + + return categories; +} + +void CSettingSection::AddCategory(const SettingCategoryPtr& category) +{ + addISetting(nullptr, category, m_categories); +} + +void CSettingSection::AddCategories(const SettingCategoryList &categories) +{ + for (const auto& category : categories) + addISetting(nullptr, category, m_categories); +} diff --git a/xbmc/settings/lib/SettingSection.h b/xbmc/settings/lib/SettingSection.h new file mode 100644 index 0000000..f8e06d6 --- /dev/null +++ b/xbmc/settings/lib/SettingSection.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISetting.h" +#include "Setting.h" +#include "SettingCategoryAccess.h" +#include "utils/logtypes.h" + +#include <string> +#include <utility> +#include <vector> + +class CSettingsManager; + +/*! + \ingroup settings + \brief Group of settings being part of a category + \sa CSettingCategory + \sa CSetting + */ +class CSettingGroup : public ISetting +{ +public: + /*! + \brief Creates a new setting group with the given identifier. + + \param id Identifier of the setting group + \param settingsManager Reference to the settings manager + */ + CSettingGroup(const std::string &id, CSettingsManager *settingsManager = nullptr); + ~CSettingGroup() override = default; + + // implementation of ISetting + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + /*! + \brief Gets the full list of settings belonging to the setting group. + + \return Full list of settings belonging to the setting group + */ + const SettingList& GetSettings() const { return m_settings; } + /*! + \brief Gets the list of settings assigned to the given setting level (or + below) and that meet the requirements conditions belonging to the setting + group. + + \param level Level the settings should be assigned to + \return List of settings belonging to the setting group + */ + SettingList GetSettings(SettingLevel level) const; + + void AddSetting(const std::shared_ptr<CSetting>& setting); + void AddSettings(const SettingList &settings); + + bool ReplaceSetting(const std::shared_ptr<const CSetting>& currentSetting, + const std::shared_ptr<CSetting>& newSetting); + + std::shared_ptr<const ISettingControl> GetControl() const { return m_control; } + std::shared_ptr<ISettingControl> GetControl() { return m_control; } + void SetControl(std::shared_ptr<ISettingControl> control) { m_control = std::move(control); } + +private: + SettingList m_settings; + std::shared_ptr<ISettingControl> m_control; + + static Logger s_logger; +}; + +using SettingGroupPtr = std::shared_ptr<CSettingGroup>; +using SettingGroupList = std::vector<SettingGroupPtr>; + +/*! + \ingroup settings + \brief Category of groups of settings being part of a section + \sa CSettingSection + \sa CSettingGroup + */ +class CSettingCategory : public ISetting +{ +public: + /*! + \brief Creates a new setting category with the given identifier. + + \param id Identifier of the setting category + \param settingsManager Reference to the settings manager + */ + CSettingCategory(const std::string &id, CSettingsManager *settingsManager = nullptr); + ~CSettingCategory() override = default; + + // implementation of ISetting + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + /*! + \brief Gets the full list of setting groups belonging to the setting + category. + + \return Full list of setting groups belonging to the setting category + */ + const SettingGroupList& GetGroups() const { return m_groups; } + /*! + \brief Gets the list of setting groups belonging to the setting category + that contain settings assigned to the given setting level (or below) and + that meet the requirements and visibility conditions. + + \param level Level the settings should be assigned to + \return List of setting groups belonging to the setting category + */ + SettingGroupList GetGroups(SettingLevel level) const; + + /*! + \brief Whether the setting category can be accessed or not. + + \return True if the setting category can be accessed, false otherwise + */ + bool CanAccess() const; + + void AddGroup(const SettingGroupPtr& group); + void AddGroupToFront(const SettingGroupPtr& group); + void AddGroups(const SettingGroupList &groups); + +private: + SettingGroupList m_groups; + CSettingCategoryAccess m_accessCondition; + + static Logger s_logger; +}; + +using SettingCategoryPtr = std::shared_ptr<CSettingCategory>; +using SettingCategoryList = std::vector<SettingCategoryPtr>; + +/*! + \ingroup settings + \brief Section of setting categories + \sa CSettings + \sa CSettingCategory + */ +class CSettingSection : public ISetting +{ +public: + /*! + \brief Creates a new setting section with the given identifier. + + \param id Identifier of the setting section + \param settingsManager Reference to the settings manager + */ + CSettingSection(const std::string &id, CSettingsManager *settingsManager = nullptr); + ~CSettingSection() override = default; + + // implementation of ISetting + bool Deserialize(const TiXmlNode *node, bool update = false) override; + + /*! + \brief Gets the full list of setting categories belonging to the setting + section. + + \return Full list of setting categories belonging to the setting section + */ + const SettingCategoryList& GetCategories() const { return m_categories; } + /*! + \brief Gets the list of setting categories belonging to the setting section + that contain settings assigned to the given setting level (or below) and + that meet the requirements and visibility conditions. + + \param level Level the settings should be assigned to + \return List of setting categories belonging to the setting section + */ + SettingCategoryList GetCategories(SettingLevel level) const; + + void AddCategory(const SettingCategoryPtr& category); + void AddCategories(const SettingCategoryList &categories); + +private: + SettingCategoryList m_categories; + + static Logger s_logger; +}; + +using SettingSectionPtr = std::shared_ptr<CSettingSection>; +using SettingSectionList = std::vector<SettingSectionPtr>; diff --git a/xbmc/settings/lib/SettingType.h b/xbmc/settings/lib/SettingType.h new file mode 100644 index 0000000..56838ab --- /dev/null +++ b/xbmc/settings/lib/SettingType.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2017-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +/*! + \ingroup settings + \brief Basic setting types available in the settings system. + */ +enum class SettingType { + Unknown = 0, + Boolean, + Integer, + Number, + String, + Action, + List +}; diff --git a/xbmc/settings/lib/SettingUpdate.cpp b/xbmc/settings/lib/SettingUpdate.cpp new file mode 100644 index 0000000..0201aab --- /dev/null +++ b/xbmc/settings/lib/SettingUpdate.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingUpdate.h" + +#include "ServiceBroker.h" +#include "SettingDefinitions.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +Logger CSettingUpdate::s_logger; + +CSettingUpdate::CSettingUpdate() +{ + if (s_logger == nullptr) + s_logger = CServiceBroker::GetLogging().GetLogger("CSettingUpdate"); +} + +bool CSettingUpdate::Deserialize(const TiXmlNode *node) +{ + if (node == nullptr) + return false; + + auto elem = node->ToElement(); + if (elem == nullptr) + return false; + + auto strType = elem->Attribute(SETTING_XML_ATTR_TYPE); + if (strType == nullptr || strlen(strType) <= 0 || !setType(strType)) + { + s_logger->warn("missing or unknown update type definition"); + return false; + } + + if (m_type == SettingUpdateType::Rename) + { + if (node->FirstChild() == nullptr || node->FirstChild()->Type() != TiXmlNode::TINYXML_TEXT) + { + s_logger->warn("missing or invalid setting id for rename update definition"); + return false; + } + + m_value = node->FirstChild()->ValueStr(); + } + + return true; +} + +bool CSettingUpdate::setType(const std::string &type) +{ + if (StringUtils::EqualsNoCase(type, "change")) + m_type = SettingUpdateType::Change; + else if (StringUtils::EqualsNoCase(type, "rename")) + m_type = SettingUpdateType::Rename; + else + return false; + + return true; +} diff --git a/xbmc/settings/lib/SettingUpdate.h b/xbmc/settings/lib/SettingUpdate.h new file mode 100644 index 0000000..65c3e86 --- /dev/null +++ b/xbmc/settings/lib/SettingUpdate.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/logtypes.h" + +#include <string> + +class TiXmlNode; + +enum class SettingUpdateType { + Unknown = 0, + Rename, + Change +}; + +class CSettingUpdate +{ +public: + CSettingUpdate(); + virtual ~CSettingUpdate() = default; + + inline bool operator<(const CSettingUpdate& rhs) const + { + return m_type < rhs.m_type && m_value < rhs.m_value; + } + + virtual bool Deserialize(const TiXmlNode *node); + + SettingUpdateType GetType() const { return m_type; } + const std::string& GetValue() const { return m_value; } + +private: + bool setType(const std::string &type); + + SettingUpdateType m_type = SettingUpdateType::Unknown; + std::string m_value; + + static Logger s_logger; +}; diff --git a/xbmc/settings/lib/SettingsManager.cpp b/xbmc/settings/lib/SettingsManager.cpp new file mode 100644 index 0000000..9ff7d61 --- /dev/null +++ b/xbmc/settings/lib/SettingsManager.cpp @@ -0,0 +1,1424 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsManager.h" + +#include "ServiceBroker.h" +#include "Setting.h" +#include "SettingDefinitions.h" +#include "SettingSection.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" +#include "utils/log.h" + +#include <algorithm> +#include <map> +#include <mutex> +#include <shared_mutex> +#include <unordered_set> +#include <utility> + +const uint32_t CSettingsManager::Version = 2; +const uint32_t CSettingsManager::MinimumSupportedVersion = 0; + +bool ParseSettingIdentifier(const std::string& settingId, std::string& categoryTag, std::string& settingTag) +{ + static const std::string Separator = "."; + + if (settingId.empty()) + return false; + + auto parts = StringUtils::Split(settingId, Separator); + if (parts.size() < 1 || parts.at(0).empty()) + return false; + + if (parts.size() == 1) + { + settingTag = parts.at(0); + return true; + } + + // get the category tag and remove it from the parts + categoryTag = parts.at(0); + parts.erase(parts.begin()); + + // put together the setting tag + settingTag = StringUtils::Join(parts, Separator); + + return true; +} + +CSettingsManager::CSettingsManager() + : m_logger(CServiceBroker::GetLogging().GetLogger("CSettingsManager")) +{ +} + +CSettingsManager::~CSettingsManager() +{ + // first clear all registered settings handler and subsettings + // implementations because we can't be sure that they are still valid + m_settingsHandlers.clear(); + m_settingCreators.clear(); + m_settingControlCreators.clear(); + + Clear(); +} + +uint32_t CSettingsManager::ParseVersion(const TiXmlElement* root) const +{ + // try to get and check the version + uint32_t version = 0; + root->QueryUnsignedAttribute(SETTING_XML_ROOT_VERSION, &version); + + return version; +} + +bool CSettingsManager::Initialize(const TiXmlElement *root) +{ + std::unique_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + if (m_initialized || root == nullptr) + return false; + + if (!StringUtils::EqualsNoCase(root->ValueStr(), SETTING_XML_ROOT)) + { + m_logger->error("error reading settings definition: doesn't contain <" SETTING_XML_ROOT + "> tag"); + return false; + } + + // try to get and check the version + uint32_t version = ParseVersion(root); + if (version == 0) + m_logger->warn("missing " SETTING_XML_ROOT_VERSION " attribute", SETTING_XML_ROOT_VERSION); + + if (MinimumSupportedVersion >= version+1) + { + m_logger->error("unable to read setting definitions from version {} (minimum version: {})", + version, MinimumSupportedVersion); + return false; + } + if (version > Version) + { + m_logger->error("unable to read setting definitions from version {} (current version: {})", + version, Version); + return false; + } + + auto sectionNode = root->FirstChild(SETTING_XML_ELM_SECTION); + while (sectionNode != nullptr) + { + std::string sectionId; + if (CSettingSection::DeserializeIdentification(sectionNode, sectionId)) + { + SettingSectionPtr section = nullptr; + auto itSection = m_sections.find(sectionId); + bool update = (itSection != m_sections.end()); + if (!update) + section = std::make_shared<CSettingSection>(sectionId, this); + else + section = itSection->second; + + if (section->Deserialize(sectionNode, update)) + AddSection(section); + else + { + m_logger->warn("unable to read section \"{}\"", sectionId); + } + } + + sectionNode = sectionNode->NextSibling(SETTING_XML_ELM_SECTION); + } + + return true; +} + +bool CSettingsManager::Load(const TiXmlElement *root, bool &updated, bool triggerEvents /* = true */, std::map<std::string, SettingPtr> *loadedSettings /* = nullptr */) +{ + std::shared_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + if (m_loaded || root == nullptr) + return false; + + if (triggerEvents && !OnSettingsLoading()) + return false; + + // try to get and check the version + uint32_t version = ParseVersion(root); + if (version == 0) + m_logger->warn("missing {} attribute", SETTING_XML_ROOT_VERSION); + + if (MinimumSupportedVersion >= version+1) + { + m_logger->error("unable to read setting values from version {} (minimum version: {})", version, + MinimumSupportedVersion); + return false; + } + if (version > Version) + { + m_logger->error("unable to read setting values from version {} (current version: {})", version, + Version); + return false; + } + + if (!Deserialize(root, updated, loadedSettings)) + return false; + + if (triggerEvents) + OnSettingsLoaded(); + + return true; +} + +bool CSettingsManager::Save( + const ISettingsValueSerializer* serializer, std::string& serializedValues) const +{ + if (serializer == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_critical); + std::shared_lock<CSharedSection> settingsLock(m_settingsCritical); + if (!m_initialized) + return false; + + if (!OnSettingsSaving()) + return false; + + serializedValues = serializer->SerializeValues(this); + + OnSettingsSaved(); + + return true; +} + +void CSettingsManager::Unload() +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded) + return; + + // needs to be set before calling CSetting::Reset() to avoid calls to + // OnSettingChanging() and OnSettingChanged() + m_loaded = false; + + for (auto& setting : m_settings) + setting.second.setting->Reset(); + + OnSettingsUnloaded(); +} + +void CSettingsManager::Clear() +{ + std::unique_lock<CSharedSection> lock(m_critical); + Unload(); + + m_settings.clear(); + m_sections.clear(); + + OnSettingsCleared(); + + m_initialized = false; +} + +bool CSettingsManager::LoadSetting(const TiXmlNode *node, const std::string &settingId) +{ + bool updated = false; + return LoadSetting(node, settingId, updated); +} + +bool CSettingsManager::LoadSetting(const TiXmlNode *node, const std::string &settingId, bool &updated) +{ + updated = false; + + if (node == nullptr) + return false; + + auto setting = GetSetting(settingId); + if (setting == nullptr) + return false; + + return LoadSetting(node, setting, updated); +} + +void CSettingsManager::SetInitialized() +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + if (m_initialized) + return; + + m_initialized = true; + + // resolve any reference settings + for (const auto& section : m_sections) + ResolveReferenceSettings(section.second); + + // remove any incomplete settings + CleanupIncompleteSettings(); + + // figure out all the dependencies between settings + for (const auto& setting : m_settings) + ResolveSettingDependencies(setting.second); +} + +void CSettingsManager::AddSection(const SettingSectionPtr& section) +{ + if (section == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + + section->CheckRequirements(); + m_sections[section->GetId()] = section; + + // get all settings and add them to the settings map + std::set<SettingPtr> newSettings; + for (const auto& category : section->GetCategories()) + { + category->CheckRequirements(); + for (auto& group : category->GetGroups()) + { + group->CheckRequirements(); + for (const auto& setting : group->GetSettings()) + { + AddSetting(setting); + + newSettings.insert(setting); + } + } + } + + if (m_initialized && !newSettings.empty()) + { + // resolve any reference settings in the new section + ResolveReferenceSettings(section); + + // cleanup any newly added incomplete settings + CleanupIncompleteSettings(); + + // resolve any dependencies for the newly added settings + for (const auto& setting : newSettings) + ResolveSettingDependencies(setting); + } +} + +bool CSettingsManager::AddSetting(const std::shared_ptr<CSetting>& setting, + const std::shared_ptr<CSettingSection>& section, + const std::shared_ptr<CSettingCategory>& category, + const std::shared_ptr<CSettingGroup>& group) +{ + if (setting == nullptr || section == nullptr || category == nullptr || group == nullptr) + return false; + + std::unique_lock<CSharedSection> lock(m_critical); + std::unique_lock<CSharedSection> settingsLock(m_settingsCritical); + + // check if a setting with the given ID already exists + if (FindSetting(setting->GetId()) != m_settings.end()) + return false; + + // if the given setting has not been added to the group yet, do it now + auto settings = group->GetSettings(); + if (std::find(settings.begin(), settings.end(), setting) == settings.end()) + group->AddSetting(setting); + + // if the given group has not been added to the category yet, do it now + auto groups = category->GetGroups(); + if (std::find(groups.begin(), groups.end(), group) == groups.end()) + category->AddGroup(group); + + // if the given category has not been added to the section yet, do it now + auto categories = section->GetCategories(); + if (std::find(categories.begin(), categories.end(), category) == categories.end()) + section->AddCategory(category); + + // check if the given section exists and matches + auto sectionPtr = GetSection(section->GetId()); + if (sectionPtr != nullptr && sectionPtr != section) + return false; + + // if the section doesn't exist yet, add it + if (sectionPtr == nullptr) + AddSection(section); + else + { + // add the setting + AddSetting(setting); + + if (m_initialized) + { + // cleanup any newly added incomplete setting + CleanupIncompleteSettings(); + + // resolve any dependencies for the newly added setting + ResolveSettingDependencies(setting); + } + } + + return true; +} + +void CSettingsManager::RegisterCallback(ISettingCallback *callback, const std::set<std::string> &settingList) +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + if (callback == nullptr) + return; + + for (const auto& setting : settingList) + { + auto itSetting = FindSetting(setting); + if (itSetting == m_settings.end()) + { + if (m_initialized) + continue; + + Setting tmpSetting = {}; + std::pair<SettingMap::iterator, bool> tmpIt = InsertSetting(setting, tmpSetting); + itSetting = tmpIt.first; + } + + itSetting->second.callbacks.insert(callback); + } +} + +void CSettingsManager::UnregisterCallback(ISettingCallback *callback) +{ + std::unique_lock<CSharedSection> lock(m_settingsCritical); + for (auto& setting : m_settings) + setting.second.callbacks.erase(callback); +} + +void CSettingsManager::RegisterSettingType(const std::string &settingType, ISettingCreator *settingCreator) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (settingType.empty() || settingCreator == nullptr) + return; + + auto creatorIt = m_settingCreators.find(settingType); + if (creatorIt == m_settingCreators.end()) + m_settingCreators.insert(std::make_pair(settingType, settingCreator)); +} + +void CSettingsManager::RegisterSettingControl(const std::string &controlType, ISettingControlCreator *settingControlCreator) +{ + if (controlType.empty() || settingControlCreator == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + auto creatorIt = m_settingControlCreators.find(controlType); + if (creatorIt == m_settingControlCreators.end()) + m_settingControlCreators.insert(std::make_pair(controlType, settingControlCreator)); +} + +void CSettingsManager::RegisterSettingsHandler(ISettingsHandler *settingsHandler, bool bFront /* = false */) +{ + if (settingsHandler == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + if (find(m_settingsHandlers.begin(), m_settingsHandlers.end(), settingsHandler) == m_settingsHandlers.end()) + { + if (bFront) + m_settingsHandlers.insert(m_settingsHandlers.begin(), settingsHandler); + else + m_settingsHandlers.emplace_back(settingsHandler); + } +} + +void CSettingsManager::UnregisterSettingsHandler(ISettingsHandler *settingsHandler) +{ + if (settingsHandler == nullptr) + return; + + std::unique_lock<CSharedSection> lock(m_critical); + auto it = std::find(m_settingsHandlers.begin(), m_settingsHandlers.end(), settingsHandler); + if (it != m_settingsHandlers.end()) + m_settingsHandlers.erase(it); +} + +void CSettingsManager::RegisterSettingOptionsFiller(const std::string &identifier, IntegerSettingOptionsFiller optionsFiller) +{ + if (identifier.empty() || optionsFiller == nullptr) + return; + + RegisterSettingOptionsFiller(identifier, reinterpret_cast<void*>(optionsFiller), SettingOptionsFillerType::Integer); +} + +void CSettingsManager::RegisterSettingOptionsFiller(const std::string &identifier, StringSettingOptionsFiller optionsFiller) +{ + if (identifier.empty() || optionsFiller == nullptr) + return; + + RegisterSettingOptionsFiller(identifier, reinterpret_cast<void*>(optionsFiller), SettingOptionsFillerType::String); +} + +void CSettingsManager::UnregisterSettingOptionsFiller(const std::string &identifier) +{ + std::unique_lock<CSharedSection> lock(m_critical); + m_optionsFillers.erase(identifier); +} + +void* CSettingsManager::GetSettingOptionsFiller(const SettingConstPtr& setting) +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (setting == nullptr) + return nullptr; + + // get the option filler's identifier + std::string filler; + if (setting->GetType() == SettingType::Integer) + filler = std::static_pointer_cast<const CSettingInt>(setting)->GetOptionsFillerName(); + else if (setting->GetType() == SettingType::String) + filler = std::static_pointer_cast<const CSettingString>(setting)->GetOptionsFillerName(); + + if (filler.empty()) + return nullptr; + + // check if such an option filler is known + auto fillerIt = m_optionsFillers.find(filler); + if (fillerIt == m_optionsFillers.end()) + return nullptr; + + if (fillerIt->second.filler == nullptr) + return nullptr; + + // make sure the option filler's type matches the setting's type + switch (fillerIt->second.type) + { + case SettingOptionsFillerType::Integer: + { + if (setting->GetType() != SettingType::Integer) + return nullptr; + + break; + } + + case SettingOptionsFillerType::String: + { + if (setting->GetType() != SettingType::String) + return nullptr; + + break; + } + + default: + return nullptr; + } + + return fillerIt->second.filler; +} + +bool CSettingsManager::HasSettings() const +{ + return !m_settings.empty(); +} + +SettingPtr CSettingsManager::GetSetting(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (id.empty()) + return nullptr; + + auto setting = FindSetting(id); + if (setting != m_settings.end()) + { + if (setting->second.setting->IsReference()) + return GetSetting(setting->second.setting->GetReferencedId()); + return setting->second.setting; + } + + m_logger->debug("requested setting ({}) was not found.", id); + return nullptr; +} + +SettingSectionList CSettingsManager::GetSections() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + SettingSectionList sections; + for (const auto& section : m_sections) + sections.push_back(section.second); + + return sections; +} + +SettingSectionPtr CSettingsManager::GetSection(std::string section) const +{ + std::shared_lock<CSharedSection> lock(m_critical); + if (section.empty()) + return nullptr; + + StringUtils::ToLower(section); + + auto sectionIt = m_sections.find(section); + if (sectionIt != m_sections.end()) + return sectionIt->second; + + m_logger->debug("requested setting section ({}) was not found.", section); + return nullptr; +} + +SettingDependencyMap CSettingsManager::GetDependencies(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + auto setting = FindSetting(id); + if (setting == m_settings.end()) + return SettingDependencyMap(); + + return setting->second.dependencies; +} + +SettingDependencyMap CSettingsManager::GetDependencies(const SettingConstPtr& setting) const +{ + if (setting == nullptr) + return SettingDependencyMap(); + + return GetDependencies(setting->GetId()); +} + +bool CSettingsManager::GetBool(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Boolean) + return false; + + return std::static_pointer_cast<CSettingBool>(setting)->GetValue(); +} + +bool CSettingsManager::SetBool(const std::string &id, bool value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Boolean) + return false; + + return std::static_pointer_cast<CSettingBool>(setting)->SetValue(value); +} + +bool CSettingsManager::ToggleBool(const std::string &id) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Boolean) + return false; + + return SetBool(id, !std::static_pointer_cast<CSettingBool>(setting)->GetValue()); +} + +int CSettingsManager::GetInt(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Integer) + return 0; + + return std::static_pointer_cast<CSettingInt>(setting)->GetValue(); +} + +bool CSettingsManager::SetInt(const std::string &id, int value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Integer) + return false; + + return std::static_pointer_cast<CSettingInt>(setting)->SetValue(value); +} + +double CSettingsManager::GetNumber(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Number) + return 0.0; + + return std::static_pointer_cast<CSettingNumber>(setting)->GetValue(); +} + +bool CSettingsManager::SetNumber(const std::string &id, double value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::Number) + return false; + + return std::static_pointer_cast<CSettingNumber>(setting)->SetValue(value); +} + +std::string CSettingsManager::GetString(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::String) + return ""; + + return std::static_pointer_cast<CSettingString>(setting)->GetValue(); +} + +bool CSettingsManager::SetString(const std::string &id, const std::string &value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::String) + return false; + + return std::static_pointer_cast<CSettingString>(setting)->SetValue(value); +} + +std::vector< std::shared_ptr<CSetting> > CSettingsManager::GetList(const std::string &id) const +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::List) + return std::vector< std::shared_ptr<CSetting> >(); + + return std::static_pointer_cast<CSettingList>(setting)->GetValue(); +} + +bool CSettingsManager::SetList(const std::string &id, const std::vector< std::shared_ptr<CSetting> > &value) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr || setting->GetType() != SettingType::List) + return false; + + return std::static_pointer_cast<CSettingList>(setting)->SetValue(value); +} + +bool CSettingsManager::SetDefault(const std::string &id) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + SettingPtr setting = GetSetting(id); + if (setting == nullptr) + return false; + + setting->Reset(); + return true; +} + +void CSettingsManager::SetDefaults() +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + for (auto& setting : m_settings) + setting.second.setting->Reset(); +} + +void CSettingsManager::AddCondition(const std::string &condition) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (condition.empty()) + return; + + m_conditions.AddCondition(condition); +} + +void CSettingsManager::AddDynamicCondition(const std::string &identifier, SettingConditionCheck condition, void *data /*= nullptr*/) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (identifier.empty() || condition == nullptr) + return; + + m_conditions.AddDynamicCondition(identifier, condition, data); +} + +void CSettingsManager::RemoveDynamicCondition(const std::string &identifier) +{ + std::unique_lock<CSharedSection> lock(m_critical); + if (identifier.empty()) + return; + + m_conditions.RemoveDynamicCondition(identifier); +} + +bool CSettingsManager::Serialize(TiXmlNode *parent) const +{ + if (parent == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_settingsCritical); + + for (const auto& setting : m_settings) + { + if (setting.second.setting->IsReference() || + setting.second.setting->GetType() == SettingType::Action) + continue; + + TiXmlElement settingElement(SETTING_XML_ELM_SETTING); + settingElement.SetAttribute(SETTING_XML_ATTR_ID, setting.second.setting->GetId()); + + // add the default attribute + if (setting.second.setting->IsDefault()) + settingElement.SetAttribute(SETTING_XML_ELM_DEFAULT, "true"); + + // add the value + TiXmlText value(setting.second.setting->ToString()); + settingElement.InsertEndChild(value); + + if (parent->InsertEndChild(settingElement) == nullptr) + { + m_logger->warn("unable to write <" SETTING_XML_ELM_SETTING " id=\"{}\"> tag", + setting.second.setting->GetId()); + continue; + } + } + + return true; +} + +bool CSettingsManager::Deserialize(const TiXmlNode *node, bool &updated, std::map<std::string, SettingPtr> *loadedSettings /* = nullptr */) +{ + updated = false; + + if (node == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_settingsCritical); + + // TODO: ideally this would be done by going through all <setting> elements + // in node but as long as we have to support the v1- format that's not possible + for (auto& setting : m_settings) + { + bool settingUpdated = false; + if (LoadSetting(node, setting.second.setting, settingUpdated)) + { + updated |= settingUpdated; + if (loadedSettings != nullptr) + loadedSettings->insert(make_pair(setting.first, setting.second.setting)); + } + } + + return true; +} + +bool CSettingsManager::OnSettingChanging(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == nullptr) + return false; + + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded) + return true; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return false; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + { + if (!callback->OnSettingChanging(setting)) + return false; + } + + // if this is a reference setting apply the same change to the referenced setting + if (setting->IsReference()) + { + std::shared_lock<CSharedSection> lock(m_settingsCritical); + auto referencedSettingIt = FindSetting(setting->GetReferencedId()); + if (referencedSettingIt != m_settings.end()) + { + Setting referencedSettingData = referencedSettingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + referencedSettingData.setting->FromString(setting->ToString()); + } + } + else if (!settingData.references.empty()) + { + // if the changed setting is referenced by other settings apply the same change to the referencing settings + std::unordered_set<SettingPtr> referenceSettings; + std::shared_lock<CSharedSection> lock(m_settingsCritical); + for (const auto& reference : settingData.references) + { + auto referenceSettingIt = FindSetting(reference); + if (referenceSettingIt != m_settings.end()) + referenceSettings.insert(referenceSettingIt->second.setting); + } + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& referenceSetting : referenceSettings) + referenceSetting->FromString(setting->ToString()); + } + + return true; +} + +void CSettingsManager::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded || setting == nullptr) + return; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + callback->OnSettingChanged(setting); + + // now handle any settings which depend on the changed setting + auto dependencies = GetDependencies(setting); + for (const auto& deps : dependencies) + { + for (const auto& dep : deps.second) + UpdateSettingByDependency(deps.first, dep); + } +} + +void CSettingsManager::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded || setting == nullptr) + return; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + callback->OnSettingAction(setting); +} + +bool CSettingsManager::OnSettingUpdate(const SettingPtr& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (setting == nullptr) + return false; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return false; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + bool ret = false; + for (auto& callback : settingData.callbacks) + ret |= callback->OnSettingUpdate(setting, oldSettingId, oldSettingNode); + + return ret; +} + +void CSettingsManager::OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) +{ + std::shared_lock<CSharedSection> lock(m_settingsCritical); + if (!m_loaded || setting == nullptr) + return; + + auto settingIt = FindSetting(setting->GetId()); + if (settingIt == m_settings.end()) + return; + + Setting settingData = settingIt->second; + // now that we have a copy of the setting's data, we can leave the lock + lock.unlock(); + + for (auto& callback : settingData.callbacks) + callback->OnSettingPropertyChanged(setting, propertyName); + + // check the changed property and if it may have an influence on the + // children of the setting + SettingDependencyType dependencyType = SettingDependencyType::Unknown; + if (StringUtils::EqualsNoCase(propertyName, "enabled")) + dependencyType = SettingDependencyType::Enable; + else if (StringUtils::EqualsNoCase(propertyName, "visible")) + dependencyType = SettingDependencyType::Visible; + + if (dependencyType != SettingDependencyType::Unknown) + { + for (const auto& child : settingIt->second.children) + UpdateSettingByDependency(child, dependencyType); + } +} + +SettingPtr CSettingsManager::CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager /* = nullptr */) const +{ + if (StringUtils::EqualsNoCase(settingType, "boolean")) + return std::make_shared<CSettingBool>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "integer")) + return std::make_shared<CSettingInt>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "number")) + return std::make_shared<CSettingNumber>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "string")) + return std::make_shared<CSettingString>(settingId, const_cast<CSettingsManager*>(this)); + else if (StringUtils::EqualsNoCase(settingType, "action")) + return std::make_shared<CSettingAction>(settingId, const_cast<CSettingsManager*>(this)); + else if (settingType.size() > 6 && + StringUtils::StartsWith(settingType, "list[") && + StringUtils::EndsWith(settingType, "]")) + { + std::string elementType = StringUtils::Mid(settingType, 5, settingType.size() - 6); + SettingPtr elementSetting = CreateSetting(elementType, settingId + ".definition", const_cast<CSettingsManager*>(this)); + if (elementSetting != nullptr) + return std::make_shared<CSettingList>(settingId, elementSetting, const_cast<CSettingsManager*>(this)); + } + + std::shared_lock<CSharedSection> lock(m_critical); + auto creator = m_settingCreators.find(settingType); + if (creator != m_settingCreators.end()) + return creator->second->CreateSetting(settingType, settingId, const_cast<CSettingsManager*>(this)); + + return nullptr; +} + +std::shared_ptr<ISettingControl> CSettingsManager::CreateControl(const std::string &controlType) const +{ + if (controlType.empty()) + return nullptr; + + std::shared_lock<CSharedSection> lock(m_critical); + auto creator = m_settingControlCreators.find(controlType); + if (creator != m_settingControlCreators.end() && creator->second != nullptr) + return creator->second->CreateControl(controlType); + + return nullptr; +} + +bool CSettingsManager::OnSettingsLoading() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + { + if (!settingsHandler->OnSettingsLoading()) + return false; + } + + return true; +} + +void CSettingsManager::OnSettingsUnloaded() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsUnloaded(); +} + +void CSettingsManager::OnSettingsLoaded() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsLoaded(); +} + +bool CSettingsManager::OnSettingsSaving() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + { + if (!settingsHandler->OnSettingsSaving()) + return false; + } + + return true; +} + +void CSettingsManager::OnSettingsSaved() const +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsSaved(); +} + +void CSettingsManager::OnSettingsCleared() +{ + std::shared_lock<CSharedSection> lock(m_critical); + for (const auto& settingsHandler : m_settingsHandlers) + settingsHandler->OnSettingsCleared(); +} + +bool CSettingsManager::LoadSetting(const TiXmlNode* node, const SettingPtr& setting, bool& updated) +{ + updated = false; + + if (node == nullptr || setting == nullptr) + return false; + + if (setting->GetType() == SettingType::Action) + return false; + + auto settingId = setting->GetId(); + if (setting->IsReference()) + settingId = setting->GetReferencedId(); + + const TiXmlElement* settingElement = nullptr; + // try to split the setting identifier into category and subsetting identifier (v1-) + std::string categoryTag, settingTag; + if (ParseSettingIdentifier(settingId, categoryTag, settingTag)) + { + auto categoryNode = node; + if (!categoryTag.empty()) + categoryNode = node->FirstChild(categoryTag); + + if (categoryNode != nullptr) + settingElement = categoryNode->FirstChildElement(settingTag); + } + + if (settingElement == nullptr) + { + // check if the setting is stored using its full setting identifier (v2+) + settingElement = node->FirstChildElement(SETTING_XML_ELM_SETTING); + while (settingElement != nullptr) + { + const auto id = settingElement->Attribute(SETTING_XML_ATTR_ID); + if (id != nullptr && settingId.compare(id) == 0) + break; + + settingElement = settingElement->NextSiblingElement(SETTING_XML_ELM_SETTING); + } + } + + if (settingElement == nullptr) + return false; + + // check if the default="true" attribute is set for the value + auto isDefaultAttribute = settingElement->Attribute(SETTING_XML_ELM_DEFAULT); + bool isDefault = isDefaultAttribute != nullptr && StringUtils::EqualsNoCase(isDefaultAttribute, "true"); + + if (!setting->FromString(settingElement->FirstChild() != nullptr ? settingElement->FirstChild()->ValueStr() : StringUtils::Empty)) + { + m_logger->warn("unable to read value of setting \"{}\"", settingId); + return false; + } + + // check if we need to perform any update logic for the setting + auto updates = setting->GetUpdates(); + for (const auto& update : updates) + updated |= UpdateSetting(node, setting, update); + + // the setting's value hasn't been updated and is the default value + // so we can reset it to the default value (in case the default value has changed) + if (!updated && isDefault) + setting->Reset(); + + return true; +} + +bool CSettingsManager::UpdateSetting(const TiXmlNode* node, + const SettingPtr& setting, + const CSettingUpdate& update) +{ + if (node == nullptr || setting == nullptr || update.GetType() == SettingUpdateType::Unknown) + return false; + + bool updated = false; + const char *oldSetting = nullptr; + const TiXmlNode *oldSettingNode = nullptr; + if (update.GetType() == SettingUpdateType::Rename) + { + if (update.GetValue().empty()) + return false; + + oldSetting = update.GetValue().c_str(); + std::string categoryTag, settingTag; + if (!ParseSettingIdentifier(oldSetting, categoryTag, settingTag)) + return false; + + auto categoryNode = node; + if (!categoryTag.empty()) + { + categoryNode = node->FirstChild(categoryTag); + if (categoryNode == nullptr) + return false; + } + + oldSettingNode = categoryNode->FirstChild(settingTag); + if (oldSettingNode == nullptr) + return false; + + if (setting->FromString(oldSettingNode->FirstChild() != nullptr ? oldSettingNode->FirstChild()->ValueStr() : StringUtils::Empty)) + updated = true; + else + m_logger->warn("unable to update \"{}\" through automatically renaming from \"{}\"", + setting->GetId(), oldSetting); + } + + updated |= OnSettingUpdate(setting, oldSetting, oldSettingNode); + return updated; +} + +void CSettingsManager::UpdateSettingByDependency(const std::string &settingId, const CSettingDependency &dependency) +{ + UpdateSettingByDependency(settingId, dependency.GetType()); +} + +void CSettingsManager::UpdateSettingByDependency(const std::string &settingId, SettingDependencyType dependencyType) +{ + auto settingIt = FindSetting(settingId); + if (settingIt == m_settings.end()) + return; + SettingPtr setting = settingIt->second.setting; + if (setting == nullptr) + return; + + switch (dependencyType) + { + case SettingDependencyType::Enable: + // just trigger the property changed callback and a call to + // CSetting::IsEnabled() will automatically determine the new + // enabled state + OnSettingPropertyChanged(setting, "enabled"); + break; + + case SettingDependencyType::Update: + { + SettingType type = setting->GetType(); + if (type == SettingType::Integer) + { + auto settingInt = std::static_pointer_cast<CSettingInt>(setting); + if (settingInt->GetOptionsType() == SettingOptionsType::Dynamic) + settingInt->UpdateDynamicOptions(); + } + else if (type == SettingType::String) + { + auto settingString = std::static_pointer_cast<CSettingString>(setting); + if (settingString->GetOptionsType() == SettingOptionsType::Dynamic) + settingString->UpdateDynamicOptions(); + } + break; + } + + case SettingDependencyType::Visible: + // just trigger the property changed callback and a call to + // CSetting::IsVisible() will automatically determine the new + // visible state + OnSettingPropertyChanged(setting, "visible"); + break; + + case SettingDependencyType::Unknown: + default: + break; + } +} + +void CSettingsManager::AddSetting(const std::shared_ptr<CSetting>& setting) +{ + setting->CheckRequirements(); + + auto addedSetting = FindSetting(setting->GetId()); + if (addedSetting == m_settings.end()) + { + Setting tmpSetting = {}; + auto tmpIt = InsertSetting(setting->GetId(), tmpSetting); + addedSetting = tmpIt.first; + } + + if (addedSetting->second.setting == nullptr) + { + addedSetting->second.setting = setting; + setting->SetCallback(this); + } +} + +void CSettingsManager::ResolveReferenceSettings(const std::shared_ptr<CSettingSection>& section) +{ + struct GroupedReferenceSettings + { + SettingPtr referencedSetting; + std::unordered_set<SettingPtr> referenceSettings; + }; + std::map<std::string, GroupedReferenceSettings> groupedReferenceSettings; + + // collect and group all reference(d) settings + auto categories = section->GetCategories(); + for (const auto& category : categories) + { + auto groups = category->GetGroups(); + for (auto& group : groups) + { + auto settings = group->GetSettings(); + for (const auto& setting : settings) + { + if (setting->IsReference()) + { + auto referencedSettingId = setting->GetReferencedId(); + auto itGroupedReferenceSetting = groupedReferenceSettings.find(referencedSettingId); + if (itGroupedReferenceSetting == groupedReferenceSettings.end()) + { + SettingPtr referencedSetting = nullptr; + auto itReferencedSetting = FindSetting(referencedSettingId); + if (itReferencedSetting == m_settings.end()) + { + m_logger->warn("missing referenced setting \"{}\"", referencedSettingId); + continue; + } + + GroupedReferenceSettings groupedReferenceSetting; + groupedReferenceSetting.referencedSetting = itReferencedSetting->second.setting; + + itGroupedReferenceSetting = groupedReferenceSettings.insert( + std::make_pair(referencedSettingId, groupedReferenceSetting)).first; + } + + itGroupedReferenceSetting->second.referenceSettings.insert(setting); + } + } + } + } + + if (groupedReferenceSettings.empty()) + return; + + // merge all reference settings into the referenced setting + for (const auto& groupedReferenceSetting : groupedReferenceSettings) + { + auto itReferencedSetting = FindSetting(groupedReferenceSetting.first); + if (itReferencedSetting == m_settings.end()) + continue; + + for (const auto& referenceSetting : groupedReferenceSetting.second.referenceSettings) + { + groupedReferenceSetting.second.referencedSetting->MergeDetails(*referenceSetting); + + itReferencedSetting->second.references.insert(referenceSetting->GetId()); + } + } + + // resolve any reference settings + for (const auto& category : categories) + { + auto groups = category->GetGroups(); + for (auto& group : groups) + { + auto settings = group->GetSettings(); + for (const auto& setting : settings) + { + if (setting->IsReference()) + { + auto referencedSettingId = setting->GetReferencedId(); + auto itGroupedReferenceSetting = groupedReferenceSettings.find(referencedSettingId); + if (itGroupedReferenceSetting != groupedReferenceSettings.end()) + { + const auto referencedSetting = itGroupedReferenceSetting->second.referencedSetting; + + // clone the referenced setting and copy the general properties of the reference setting + auto clonedReferencedSetting = referencedSetting->Clone(setting->GetId()); + clonedReferencedSetting->SetReferencedId(referencedSettingId); + clonedReferencedSetting->MergeBasics(*setting); + + group->ReplaceSetting(setting, clonedReferencedSetting); + + // update the setting + auto itReferenceSetting = FindSetting(setting->GetId()); + if (itReferenceSetting != m_settings.end()) + itReferenceSetting->second.setting = clonedReferencedSetting; + } + } + } + } + } +} + +void CSettingsManager::CleanupIncompleteSettings() +{ + // remove any empty and reference settings + for (auto setting = m_settings.begin(); setting != m_settings.end(); ) + { + auto tmpIterator = setting++; + if (tmpIterator->second.setting == nullptr) + { + m_logger->warn("removing empty setting \"{}\"", tmpIterator->first); + m_settings.erase(tmpIterator); + } + } +} + +void CSettingsManager::RegisterSettingOptionsFiller(const std::string &identifier, void *filler, SettingOptionsFillerType type) +{ + std::unique_lock<CSharedSection> lock(m_critical); + auto it = m_optionsFillers.find(identifier); + if (it != m_optionsFillers.end()) + return; + + SettingOptionsFiller optionsFiller = { filler, type }; + m_optionsFillers.insert(make_pair(identifier, optionsFiller)); +} + +void CSettingsManager::ResolveSettingDependencies(const std::shared_ptr<CSetting>& setting) +{ + if (setting == nullptr) + return; + + ResolveSettingDependencies(FindSetting(setting->GetId())->second); +} + +void CSettingsManager::ResolveSettingDependencies(const Setting& setting) +{ + if (setting.setting == nullptr) + return; + + // if the setting has a parent setting, add it to its children + auto parentSettingId = setting.setting->GetParent(); + if (!parentSettingId.empty()) + { + auto itParentSetting = FindSetting(parentSettingId); + if (itParentSetting != m_settings.end()) + itParentSetting->second.children.insert(setting.setting->GetId()); + } + + // handle all dependencies of the setting + const auto& dependencies = setting.setting->GetDependencies(); + for (const auto& deps : dependencies) + { + const auto settingIds = deps.GetSettings(); + for (const auto& settingId : settingIds) + { + auto settingIt = FindSetting(settingId); + if (settingIt == m_settings.end()) + continue; + + bool newDep = true; + auto& settingDeps = settingIt->second.dependencies[setting.setting->GetId()]; + for (const auto& dep : settingDeps) + { + if (dep.GetType() == deps.GetType()) + { + newDep = false; + break; + } + } + + if (newDep) + settingDeps.push_back(deps); + } + } +} + +CSettingsManager::SettingMap::const_iterator CSettingsManager::FindSetting(std::string settingId) const +{ + StringUtils::ToLower(settingId); + return m_settings.find(settingId); +} + +CSettingsManager::SettingMap::iterator CSettingsManager::FindSetting(std::string settingId) +{ + StringUtils::ToLower(settingId); + return m_settings.find(settingId); +} + +std::pair<CSettingsManager::SettingMap::iterator, bool> CSettingsManager::InsertSetting(std::string settingId, const Setting& setting) +{ + StringUtils::ToLower(settingId); + return m_settings.insert(std::make_pair(settingId, setting)); +} diff --git a/xbmc/settings/lib/SettingsManager.h b/xbmc/settings/lib/SettingsManager.h new file mode 100644 index 0000000..161c1a9 --- /dev/null +++ b/xbmc/settings/lib/SettingsManager.h @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "ISettingCallback.h" +#include "ISettingControlCreator.h" +#include "ISettingCreator.h" +#include "ISettingsHandler.h" +#include "ISettingsValueSerializer.h" +#include "Setting.h" +#include "SettingConditions.h" +#include "SettingDefinitions.h" +#include "SettingDependency.h" +#include "threads/SharedSection.h" +#include "utils/logtypes.h" + +#include <map> +#include <set> +#include <unordered_set> +#include <vector> + +class CSettingCategory; +class CSettingGroup; +class CSettingSection; +class CSettingUpdate; + +class TiXmlElement; +class TiXmlNode; + +/*! + \ingroup settings + \brief Settings manager responsible for initializing, loading and handling + all settings. + */ +class CSettingsManager : public ISettingCreator, + public ISettingControlCreator, + private ISettingCallback, + private ISettingsHandler +{ +public: + /*! + \brief Creates a new (uninitialized) settings manager. + */ + CSettingsManager(); + ~CSettingsManager() override; + + static const uint32_t Version; + static const uint32_t MinimumSupportedVersion; + + // implementation of ISettingCreator + std::shared_ptr<CSetting> CreateSetting(const std::string &settingType, const std::string &settingId, CSettingsManager *settingsManager = nullptr) const override; + + // implementation of ISettingControlCreator + std::shared_ptr<ISettingControl> CreateControl(const std::string &controlType) const override; + + uint32_t GetVersion() const { return Version; } + uint32_t GetMinimumSupportedVersion() const { return MinimumSupportedVersion; } + + /*! + \brief Try to get the version of the setting definitions/values represented by the given XML element. + + \param root XML element representing setting definitions/values + \return Version of the setting definitions/values or 0 if no version has been specified + */ + uint32_t ParseVersion(const TiXmlElement* root) const; + + /*! + \brief Initializes the settings manager using the setting definitions + represented by the given XML element. + + \param root XML element representing setting definitions + \return True if the XML element was successfully deserialized into setting definitions, false otherwise + */ + bool Initialize(const TiXmlElement *root); + /*! + \brief Loads setting values from the given XML element. + + \param root XML element containing setting values + \param updated Whether some settings were automatically updated + \param triggerEvents Whether to trigger ISettingCallback methods + \param loadedSettings A list to fill with all the successfully loaded settings + \return True if the setting values were successfully loaded, false otherwise + */ + bool Load(const TiXmlElement *root, bool &updated, bool triggerEvents = true, std::map<std::string, std::shared_ptr<CSetting>> *loadedSettings = nullptr); + /*! + \brief Saves the setting values using the given serializer. + + \param serializer Settings value serializer to use + \return True if the setting values were successfully serialized, false otherwise + */ + bool Save(const ISettingsValueSerializer* serializer, std::string& serializedValues) const; + /*! + \brief Unloads the previously loaded setting values. + + The values of all the settings are reset to their default values. + */ + void Unload(); + /*! + \brief Clears the complete settings manager. + + This removes all initialized settings, groups, categories and sections and + returns to the uninitialized state. Any registered callbacks or + implementations stay registered. + */ + void Clear(); + + /*! + \brief Loads the setting being represented by the given XML node with the + given identifier. + + \param node XML node representing the setting to load + \param settingId Setting identifier + \return True if the setting was successfully loaded from the given XML node, false otherwise + */ + bool LoadSetting(const TiXmlNode *node, const std::string &settingId); + + /*! + \brief Loads the setting being represented by the given XML node with the + given identifier. + + \param node XML node representing the setting to load + \param settingId Setting identifier + \param updated Set to true if the setting's value was updated + \return True if the setting was successfully loaded from the given XML node, false otherwise + */ + bool LoadSetting(const TiXmlNode *node, const std::string &settingId, bool &updated); + + /*! + \brief Tells the settings system that the initialization is complete. + + Setting values can only be loaded after a complete and successful + initialization of the settings system. + */ + void SetInitialized(); + /*! + \brief Returns whether the settings system has been initialized or not. + */ + bool IsInitialized() const { return m_initialized; } + /*! + \brief Tells the settings system that all setting values + have been loaded. + + This manual trigger is necessary to enable the ISettingCallback methods + being executed. + */ + void SetLoaded() { m_loaded = true; } + /*! + \brief Returns whether the settings system has been loaded or not. + */ + bool IsLoaded() const { return m_loaded; } + + /*! + \brief Adds the given section, its categories, groups and settings. + + This is possible before and after the setting definitions have been + initialized. + */ + void AddSection(const std::shared_ptr<CSettingSection>& section); + + /*! + \brief Adds the given setting to the given group in the given category in + the given section; + + If the given section has not been added yet, it is added. If the given + category has not been added to the given section yet, it is added. If the + given group has not been added to the given category yet, it is added. If + the given setting has not been added to the given group yet, it is added. + + This is possible before and after the setting definitions have been + initialized. + + \param setting New setting to be added + \param section Section the new setting should be added to + \param category Category the new setting should be added to + \param group Group the new setting should be added to + \return True if the setting has been added, false otherwise + */ + bool AddSetting(const std::shared_ptr<CSetting>& setting, + const std::shared_ptr<CSettingSection>& section, + const std::shared_ptr<CSettingCategory>& category, + const std::shared_ptr<CSettingGroup>& group); + + /*! + \brief Registers the given ISettingCallback implementation to be triggered + for the given list of settings. + + \param settingsHandler ISettingsHandler implementation + \param settingList List of settings to trigger the given ISettingCallback implementation + */ + void RegisterCallback(ISettingCallback *callback, const std::set<std::string> &settingList); + /*! + \brief Unregisters the given ISettingCallback implementation. + + \param callback ISettingCallback implementation + */ + void UnregisterCallback(ISettingCallback *callback); + + /*! + \brief Registers a custom setting type and its ISettingCreator + implementation. + + When a setting definition for a registered custom setting type is found its + ISettingCreator implementation is called to create and deserialize the + setting definition. + + \param settingType String representation of the custom setting type + \param settingCreator ISettingCreator implementation + */ + void RegisterSettingType(const std::string &settingType, ISettingCreator *settingCreator); + + /*! + \brief Registers a custom setting control type and its + ISettingControlCreator implementation + + When a setting control definition for a registered custom setting control + type is found its ISettingControlCreator implementation is called to create + and deserialize the setting control definition. + + \param controlType String representation of the custom setting control type + \param settingControlCreator ISettingControlCreator implementation + */ + void RegisterSettingControl(const std::string &controlType, ISettingControlCreator *settingControlCreator); + + /*! + \brief Registers the given ISettingsHandler implementation. + + \param settingsHandler ISettingsHandler implementation + \param bFront If True, insert the handler in front of other registered handlers, insert at the end otherwise. + */ + void RegisterSettingsHandler(ISettingsHandler *settingsHandler, bool bFront = false); + /*! + \brief Unregisters the given ISettingsHandler implementation. + + \param settingsHandler ISettingsHandler implementation + */ + void UnregisterSettingsHandler(ISettingsHandler *settingsHandler); + + /*! + \brief Registers the given integer setting options filler under the given identifier. + + \param identifier Setting options filler identifier + \param optionsFiller Integer setting options filler implementation + */ + void RegisterSettingOptionsFiller(const std::string &identifier, IntegerSettingOptionsFiller optionsFiller); + /*! + \brief Registers the given string setting options filler under the given identifier. + + \param identifier Setting options filler identifier + \param optionsFiller String setting options filler implementation + */ + void RegisterSettingOptionsFiller(const std::string &identifier, StringSettingOptionsFiller optionsFiller); + /*! + \brief Unregisters the setting options filler registered under the given identifier. + + \param identifier Setting options filler identifier + */ + void UnregisterSettingOptionsFiller(const std::string &identifier); + /*! + \brief Gets the implementation of the setting options filler used by the + given setting. + + \param setting Setting object + \return Implementation of the setting options filler (either IntegerSettingOptionsFiller or StringSettingOptionsFiller) + */ + void* GetSettingOptionsFiller(const std::shared_ptr<const CSetting>& setting); + + /*! + \brief Checks whether any settings have been initialized. + + \return True if at least one setting has been initialized, false otherwise*/ + bool HasSettings() const; + + /*! + \brief Gets the setting with the given identifier. + + \param id Setting identifier + \return Setting object with the given identifier or nullptr if the identifier is unknown + */ + std::shared_ptr<CSetting> GetSetting(const std::string &id) const; + /*! + \brief Gets the full list of setting sections. + + \return List of setting sections + */ + std::vector<std::shared_ptr<CSettingSection>> GetSections() const; + /*! + \brief Gets the setting section with the given identifier. + + \param section Setting section identifier + \return Setting section with the given identifier or nullptr if the identifier is unknown + */ + std::shared_ptr<CSettingSection> GetSection(std::string section) const; + /*! + \brief Gets a map of settings (and their dependencies) which depend on + the setting with the given identifier. + + It is important to note that the returned dependencies are not the + dependencies of the setting with the given identifier but the settings + (and their dependencies) which depend on the setting with the given + identifier. + + \param id Setting identifier + \return Map of settings (and their dependencies) which depend on the setting with the given identifier + */ + SettingDependencyMap GetDependencies(const std::string &id) const; + /*! + \brief Gets a map of settings (and their dependencies) which depend on + the given setting. + + It is important to note that the returned dependencies are not the + dependencies of the given setting but the settings (and their dependencies) + which depend on the given setting. + + \param setting Setting object + \return Map of settings (and their dependencies) which depend on the given setting + */ + SettingDependencyMap GetDependencies(const std::shared_ptr<const CSetting>& setting) const; + + /*! + \brief Gets the boolean value of the setting with the given identifier. + + \param id Setting identifier + \return Boolean value of the setting with the given identifier + */ + bool GetBool(const std::string &id) const; + /*! + \brief Gets the integer value of the setting with the given identifier. + + \param id Setting identifier + \return Integer value of the setting with the given identifier + */ + int GetInt(const std::string &id) const; + /*! + \brief Gets the real number value of the setting with the given identifier. + + \param id Setting identifier + \return Real number value of the setting with the given identifier + */ + double GetNumber(const std::string &id) const; + /*! + \brief Gets the string value of the setting with the given identifier. + + \param id Setting identifier + \return String value of the setting with the given identifier + */ + std::string GetString(const std::string &id) const; + /*! + \brief Gets the values of the list setting with the given identifier. + + \param id Setting identifier + \return List of values of the setting with the given identifier + */ + std::vector< std::shared_ptr<CSetting> > GetList(const std::string &id) const; + + /*! + \brief Sets the boolean value of the setting with the given identifier. + + \param id Setting identifier + \param value Boolean value to set + \return True if setting the value was successful, false otherwise + */ + bool SetBool(const std::string &id, bool value); + /*! + \brief Toggles the boolean value of the setting with the given identifier. + + \param id Setting identifier + \return True if toggling the boolean value was successful, false otherwise + */ + bool ToggleBool(const std::string &id); + /*! + \brief Sets the integer value of the setting with the given identifier. + + \param id Setting identifier + \param value Integer value to set + \return True if setting the value was successful, false otherwise + */ + bool SetInt(const std::string &id, int value); + /*! + \brief Sets the real number value of the setting with the given identifier. + + \param id Setting identifier + \param value Real number value to set + \return True if setting the value was successful, false otherwise + */ + bool SetNumber(const std::string &id, double value); + /*! + \brief Sets the string value of the setting with the given identifier. + + \param id Setting identifier + \param value String value to set + \return True if setting the value was successful, false otherwise + */ + bool SetString(const std::string &id, const std::string &value); + /*! + \brief Sets the values of the list setting with the given identifier. + + \param id Setting identifier + \param value Values to set + \return True if setting the values was successful, false otherwise + */ + bool SetList(const std::string &id, const std::vector< std::shared_ptr<CSetting> > &value); + + /*! + \brief Sets the value of the setting to its default. + + \param id Setting identifier + \return True if setting the value to its default was successful, false otherwise + */ + bool SetDefault(const std::string &id); + /*! + \brief Sets the value of all settings to their default. + */ + void SetDefaults(); + + /*! + \brief Gets the setting conditions manager used by the settings manager. + + \return Setting conditions manager used by the settings manager. + */ + const CSettingConditionsManager& GetConditions() const { return m_conditions; } + /*! + \brief Adds the given static condition. + + A static condition is just a string. If a static condition is evaluated, + the result depends on whether the condition's value is defined or not. + + \param condition Static condition string/value + */ + void AddCondition(const std::string &condition); + /*! + \brief Adds the given dynamic condition. + + A dynamic condition has an identifier and an implementation which is + triggered when the condition is evaluated. + + \param identifier Identifier of the dynamic condition + \param condition Implementation of the dynamic condition + \param data Opaque data pointer, will be passed back to SettingConditionCheck function + */ + void AddDynamicCondition(const std::string &identifier, SettingConditionCheck condition, void *data = nullptr); + + /*! + \brief Removes the given dynamic condition. + + \param identifier Identifier of the dynamic condition + */ + void RemoveDynamicCondition(const std::string &identifier); + +private: + // implementation of ISettingCallback + bool OnSettingChanging(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + bool OnSettingUpdate(const std::shared_ptr<CSetting>& setting, + const char* oldSettingId, + const TiXmlNode* oldSettingNode) override; + void OnSettingPropertyChanged(const std::shared_ptr<const CSetting>& setting, + const char* propertyName) override; + + // implementation of ISettingsHandler + bool OnSettingsLoading() override; + void OnSettingsLoaded() override; + void OnSettingsUnloaded() override; + bool OnSettingsSaving() const override; + void OnSettingsSaved() const override; + void OnSettingsCleared() override; + + bool Serialize(TiXmlNode *parent) const; + bool Deserialize(const TiXmlNode *node, bool &updated, std::map<std::string, std::shared_ptr<CSetting>> *loadedSettings = nullptr); + + bool LoadSetting(const TiXmlNode* node, const std::shared_ptr<CSetting>& setting, bool& updated); + bool UpdateSetting(const TiXmlNode* node, + const std::shared_ptr<CSetting>& setting, + const CSettingUpdate& update); + void UpdateSettingByDependency(const std::string &settingId, const CSettingDependency &dependency); + void UpdateSettingByDependency(const std::string &settingId, SettingDependencyType dependencyType); + + void AddSetting(const std::shared_ptr<CSetting>& setting); + + void ResolveReferenceSettings(const std::shared_ptr<CSettingSection>& section); + void CleanupIncompleteSettings(); + + enum class SettingOptionsFillerType { + Unknown = 0, + Integer, + String + }; + + void RegisterSettingOptionsFiller(const std::string &identifier, void *filler, SettingOptionsFillerType type); + + using CallbackSet = std::set<ISettingCallback *>; + struct Setting { + std::shared_ptr<CSetting> setting; + SettingDependencyMap dependencies; + std::set<std::string> children; + CallbackSet callbacks; + std::unordered_set<std::string> references; + }; + + using SettingMap = std::map<std::string, Setting>; + + void ResolveSettingDependencies(const std::shared_ptr<CSetting>& setting); + void ResolveSettingDependencies(const Setting& setting); + + SettingMap::const_iterator FindSetting(std::string settingId) const; + SettingMap::iterator FindSetting(std::string settingId); + std::pair<SettingMap::iterator, bool> InsertSetting(std::string settingId, const Setting& setting); + + bool m_initialized = false; + bool m_loaded = false; + + SettingMap m_settings; + using SettingSectionMap = std::map<std::string, std::shared_ptr<CSettingSection>>; + SettingSectionMap m_sections; + + using SettingCreatorMap = std::map<std::string, ISettingCreator*>; + SettingCreatorMap m_settingCreators; + + using SettingControlCreatorMap = std::map<std::string, ISettingControlCreator*>; + SettingControlCreatorMap m_settingControlCreators; + + using SettingsHandlers = std::vector<ISettingsHandler*>; + SettingsHandlers m_settingsHandlers; + + CSettingConditionsManager m_conditions; + + struct SettingOptionsFiller { + void *filler; + SettingOptionsFillerType type; + }; + using SettingOptionsFillerMap = std::map<std::string, SettingOptionsFiller>; + SettingOptionsFillerMap m_optionsFillers; + + mutable CSharedSection m_critical; + mutable CSharedSection m_settingsCritical; + + Logger m_logger; +}; diff --git a/xbmc/settings/windows/CMakeLists.txt b/xbmc/settings/windows/CMakeLists.txt new file mode 100644 index 0000000..c1b69ac --- /dev/null +++ b/xbmc/settings/windows/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES GUIControlSettings.cpp + GUIWindowSettings.cpp + GUIWindowSettingsCategory.cpp + GUIWindowSettingsScreenCalibration.cpp) + +set(HEADERS GUIControlSettings.h + GUIWindowSettings.h + GUIWindowSettingsCategory.h + GUIWindowSettingsScreenCalibration.h) + +core_add_library(settings_windows) diff --git a/xbmc/settings/windows/GUIControlSettings.cpp b/xbmc/settings/windows/GUIControlSettings.cpp new file mode 100644 index 0000000..fcbf1fa --- /dev/null +++ b/xbmc/settings/windows/GUIControlSettings.cpp @@ -0,0 +1,1798 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIControlSettings.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "addons/AddonManager.h" +#include "addons/gui/GUIWindowAddonBrowser.h" +#include "addons/settings/SettingUrlEncodedString.h" +#include "dialogs/GUIDialogColorPicker.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogNumeric.h" +#include "dialogs/GUIDialogSelect.h" +#include "dialogs/GUIDialogSlider.h" +#include "guilib/GUIColorButtonControl.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUIImage.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUILabelControl.h" +#include "guilib/GUIRadioButtonControl.h" +#include "guilib/GUISettingsSliderControl.h" +#include "guilib/GUISpinControlEx.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/SettingAddon.h" +#include "settings/SettingControl.h" +#include "settings/SettingDateTime.h" +#include "settings/SettingPath.h" +#include "settings/SettingUtils.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "storage/MediaManager.h" +#include "utils/FileExtensionProvider.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <set> +#include <utility> + +using namespace ADDON; + +static std::string Localize(std::uint32_t code, + ILocalizer* localizer, + const std::string& addonId = "") +{ + if (localizer == nullptr) + return ""; + + if (!addonId.empty()) + { + std::string label = g_localizeStrings.GetAddonString(addonId, code); + if (!label.empty()) + return label; + } + + return localizer->Localize(code); +} + +template<typename TValueType> +static CFileItemPtr GetFileItem(const std::string& label, + const std::string& label2, + const TValueType& value, + const std::vector<std::pair<std::string, CVariant>>& properties, + const std::set<TValueType>& selectedValues) +{ + CFileItemPtr item(new CFileItem(label)); + item->SetProperty("value", value); + item->SetLabel2(label2); + + for (const auto& prop : properties) + item->SetProperty(prop.first, prop.second); + + if (selectedValues.find(value) != selectedValues.end()) + item->Select(true); + + return item; +} + +template<class SettingOption> +static bool CompareSettingOptionAseconding(const SettingOption& lhs, const SettingOption& rhs) +{ + return StringUtils::CompareNoCase(lhs.label, rhs.label) < 0; +} + +template<class SettingOption> +static bool CompareSettingOptionDeseconding(const SettingOption& lhs, const SettingOption& rhs) +{ + return StringUtils::CompareNoCase(lhs.label, rhs.label) > 0; +} + +static bool GetIntegerOptions(const SettingConstPtr& setting, + IntegerSettingOptions& options, + std::set<int>& selectedOptions, + ILocalizer* localizer, + bool updateOptions) +{ + std::shared_ptr<const CSettingInt> pSettingInt = NULL; + if (setting->GetType() == SettingType::Integer) + pSettingInt = std::static_pointer_cast<const CSettingInt>(setting); + else if (setting->GetType() == SettingType::List) + { + std::shared_ptr<const CSettingList> settingList = + std::static_pointer_cast<const CSettingList>(setting); + if (settingList->GetElementType() != SettingType::Integer) + return false; + + pSettingInt = std::static_pointer_cast<const CSettingInt>(settingList->GetDefinition()); + } + + switch (pSettingInt->GetOptionsType()) + { + case SettingOptionsType::StaticTranslatable: + { + const TranslatableIntegerSettingOptions& settingOptions = + pSettingInt->GetTranslatableOptions(); + for (const auto& option : settingOptions) + options.push_back( + IntegerSettingOption(Localize(option.label, localizer, option.addonId), option.value)); + break; + } + + case SettingOptionsType::Static: + { + const IntegerSettingOptions& settingOptions = pSettingInt->GetOptions(); + options.insert(options.end(), settingOptions.begin(), settingOptions.end()); + break; + } + + case SettingOptionsType::Dynamic: + { + IntegerSettingOptions settingOptions; + if (updateOptions) + settingOptions = std::const_pointer_cast<CSettingInt>(pSettingInt)->UpdateDynamicOptions(); + else + settingOptions = pSettingInt->GetDynamicOptions(); + options.insert(options.end(), settingOptions.begin(), settingOptions.end()); + break; + } + + case SettingOptionsType::Unknown: + default: + { + std::shared_ptr<const CSettingControlFormattedRange> control = + std::static_pointer_cast<const CSettingControlFormattedRange>(pSettingInt->GetControl()); + for (int i = pSettingInt->GetMinimum(); i <= pSettingInt->GetMaximum(); + i += pSettingInt->GetStep()) + { + std::string strLabel; + if (i == pSettingInt->GetMinimum() && control->GetMinimumLabel() > -1) + strLabel = Localize(control->GetMinimumLabel(), localizer); + else if (control->GetFormatLabel() > -1) + strLabel = StringUtils::Format(Localize(control->GetFormatLabel(), localizer), i); + else + strLabel = StringUtils::Format(control->GetFormatString(), i); + + options.push_back(IntegerSettingOption(strLabel, i)); + } + + break; + } + } + + switch (pSettingInt->GetOptionsSort()) + { + case SettingOptionsSort::Ascending: + std::sort(options.begin(), options.end(), + CompareSettingOptionAseconding<IntegerSettingOption>); + break; + + case SettingOptionsSort::Descending: + std::sort(options.begin(), options.end(), + CompareSettingOptionDeseconding<IntegerSettingOption>); + break; + + case SettingOptionsSort::NoSorting: + default: + break; + } + + // this must be done after potentially calling CSettingInt::UpdateDynamicOptions() because it can + // change the value of the setting + if (setting->GetType() == SettingType::Integer) + selectedOptions.insert(pSettingInt->GetValue()); + else if (setting->GetType() == SettingType::List) + { + std::vector<CVariant> list = + CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting)); + for (const auto& itValue : list) + selectedOptions.insert((int)itValue.asInteger()); + } + else + return false; + + return true; +} + +static bool GetStringOptions(const SettingConstPtr& setting, + StringSettingOptions& options, + std::set<std::string>& selectedOptions, + ILocalizer* localizer, + bool updateOptions) +{ + std::shared_ptr<const CSettingString> pSettingString = NULL; + if (setting->GetType() == SettingType::String) + pSettingString = std::static_pointer_cast<const CSettingString>(setting); + else if (setting->GetType() == SettingType::List) + { + std::shared_ptr<const CSettingList> settingList = + std::static_pointer_cast<const CSettingList>(setting); + if (settingList->GetElementType() != SettingType::String) + return false; + + pSettingString = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition()); + } + + switch (pSettingString->GetOptionsType()) + { + case SettingOptionsType::StaticTranslatable: + { + const TranslatableStringSettingOptions& settingOptions = + pSettingString->GetTranslatableOptions(); + for (const auto& option : settingOptions) + options.push_back(StringSettingOption(Localize(option.first, localizer), option.second)); + break; + } + + case SettingOptionsType::Static: + { + const StringSettingOptions& settingOptions = pSettingString->GetOptions(); + options.insert(options.end(), settingOptions.begin(), settingOptions.end()); + break; + } + + case SettingOptionsType::Dynamic: + { + StringSettingOptions settingOptions; + if (updateOptions) + settingOptions = + std::const_pointer_cast<CSettingString>(pSettingString)->UpdateDynamicOptions(); + else + settingOptions = pSettingString->GetDynamicOptions(); + options.insert(options.end(), settingOptions.begin(), settingOptions.end()); + break; + } + + case SettingOptionsType::Unknown: + default: + return false; + } + + switch (pSettingString->GetOptionsSort()) + { + case SettingOptionsSort::Ascending: + std::sort(options.begin(), options.end(), + CompareSettingOptionAseconding<StringSettingOption>); + break; + + case SettingOptionsSort::Descending: + std::sort(options.begin(), options.end(), + CompareSettingOptionDeseconding<StringSettingOption>); + break; + + case SettingOptionsSort::NoSorting: + default: + break; + } + + // this must be done after potentially calling CSettingString::UpdateDynamicOptions() because it + // can change the value of the setting + if (setting->GetType() == SettingType::String) + selectedOptions.insert(pSettingString->GetValue()); + else if (setting->GetType() == SettingType::List) + { + std::vector<CVariant> list = + CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(setting)); + for (const auto& itValue : list) + selectedOptions.insert(itValue.asString()); + } + else + return false; + + return true; +} + +CGUIControlBaseSetting::CGUIControlBaseSetting(int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : m_id(id), + m_pSetting(std::move(pSetting)), + m_localizer(localizer), + m_delayed(false), + m_valid(true) +{ +} + +bool CGUIControlBaseSetting::IsEnabled() const +{ + return m_pSetting != NULL && m_pSetting->IsEnabled(); +} + +void CGUIControlBaseSetting::UpdateFromControl() +{ + Update(true, true); +} + +void CGUIControlBaseSetting::UpdateFromSetting(bool updateDisplayOnly /* = false */) +{ + Update(false, updateDisplayOnly); +} + +std::string CGUIControlBaseSetting::Localize(std::uint32_t code) const +{ + return ::Localize(code, m_localizer); +} + +void CGUIControlBaseSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (fromControl || updateDisplayOnly) + return; + + CGUIControl* control = GetControl(); + if (control == NULL) + return; + + control->SetEnabled(IsEnabled()); + if (m_pSetting) + control->SetVisible(m_pSetting->IsVisible()); + SetValid(true); +} + +CGUIControlRadioButtonSetting::CGUIControlRadioButtonSetting(CGUIRadioButtonControl* pRadioButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, std::move(pSetting), localizer) +{ + m_pRadioButton = pRadioButton; + if (m_pRadioButton == NULL) + return; + + m_pRadioButton->SetID(id); +} + +CGUIControlRadioButtonSetting::~CGUIControlRadioButtonSetting() = default; + +bool CGUIControlRadioButtonSetting::OnClick() +{ + SetValid(std::static_pointer_cast<CSettingBool>(m_pSetting) + ->SetValue(!std::static_pointer_cast<CSettingBool>(m_pSetting)->GetValue())); + return IsValid(); +} + +void CGUIControlRadioButtonSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (fromControl || m_pRadioButton == NULL) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + + m_pRadioButton->SetSelected(std::static_pointer_cast<CSettingBool>(m_pSetting)->GetValue()); +} + +CGUIControlColorButtonSetting::CGUIControlColorButtonSetting( + CGUIColorButtonControl* pColorControl, + int id, + const std::shared_ptr<CSetting>& pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, pSetting, localizer) +{ + m_pColorButton = pColorControl; + if (!m_pColorButton) + return; + + m_pColorButton->SetID(id); +} + +CGUIControlColorButtonSetting::~CGUIControlColorButtonSetting() = default; + +bool CGUIControlColorButtonSetting::OnClick() +{ + if (!m_pColorButton) + return false; + + std::shared_ptr<CSettingString> settingHexColor = + std::static_pointer_cast<CSettingString>(m_pSetting); + + CGUIDialogColorPicker* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogColorPicker>( + WINDOW_DIALOG_COLOR_PICKER); + if (!dialog) + return false; + + dialog->Reset(); + dialog->SetHeading(CVariant{Localize(m_pSetting->GetLabel())}); + dialog->LoadColors(); + std::string hexColor; + if (settingHexColor) + hexColor = settingHexColor.get()->GetValue(); + dialog->SetSelectedColor(hexColor); + dialog->Open(); + + if (!dialog->IsConfirmed()) + return false; + + SetValid( + std::static_pointer_cast<CSettingString>(m_pSetting)->SetValue(dialog->GetSelectedColor())); + return IsValid(); +} + +void CGUIControlColorButtonSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (fromControl || !m_pColorButton) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + // Set the color to apply to the preview color box + m_pColorButton->SetImageBoxColor( + std::static_pointer_cast<CSettingString>(m_pSetting)->GetValue()); +} + +CGUIControlSpinExSetting::CGUIControlSpinExSetting(CGUISpinControlEx* pSpin, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, std::move(pSetting), localizer) +{ + m_pSpin = pSpin; + if (m_pSpin == NULL) + return; + + m_pSpin->SetID(id); + + const std::string& controlFormat = m_pSetting->GetControl()->GetFormat(); + if (controlFormat == "number") + { + std::shared_ptr<CSettingNumber> pSettingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + m_pSpin->SetType(SPIN_CONTROL_TYPE_FLOAT); + m_pSpin->SetFloatRange(static_cast<float>(pSettingNumber->GetMinimum()), + static_cast<float>(pSettingNumber->GetMaximum())); + m_pSpin->SetFloatInterval(static_cast<float>(pSettingNumber->GetStep())); + } + else if (controlFormat == "integer") + m_pSpin->SetType(SPIN_CONTROL_TYPE_TEXT); + else if (controlFormat == "string") + { + m_pSpin->SetType(SPIN_CONTROL_TYPE_TEXT); + + if (m_pSetting->GetType() == SettingType::Integer) + FillIntegerSettingControl(false); + else if (m_pSetting->GetType() == SettingType::Number) + { + std::shared_ptr<CSettingNumber> pSettingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + std::shared_ptr<const CSettingControlFormattedRange> control = + std::static_pointer_cast<const CSettingControlFormattedRange>(m_pSetting->GetControl()); + int index = 0; + for (double value = pSettingNumber->GetMinimum(); value <= pSettingNumber->GetMaximum(); + value += pSettingNumber->GetStep(), index++) + { + std::string strLabel; + if (value == pSettingNumber->GetMinimum() && control->GetMinimumLabel() > -1) + strLabel = Localize(control->GetMinimumLabel()); + else if (control->GetFormatLabel() > -1) + strLabel = StringUtils::Format(Localize(control->GetFormatLabel()), value); + else + strLabel = StringUtils::Format(control->GetFormatString(), value); + + m_pSpin->AddLabel(strLabel, index); + } + } + } +} + +CGUIControlSpinExSetting::~CGUIControlSpinExSetting() = default; + +bool CGUIControlSpinExSetting::OnClick() +{ + if (m_pSpin == NULL) + return false; + + switch (m_pSetting->GetType()) + { + case SettingType::Integer: + SetValid(std::static_pointer_cast<CSettingInt>(m_pSetting)->SetValue(m_pSpin->GetValue())); + break; + + case SettingType::Number: + { + auto pSettingNumber = std::static_pointer_cast<CSettingNumber>(m_pSetting); + const auto& controlFormat = m_pSetting->GetControl()->GetFormat(); + if (controlFormat == "number") + SetValid(pSettingNumber->SetValue(static_cast<double>(m_pSpin->GetFloatValue()))); + else + SetValid(pSettingNumber->SetValue(pSettingNumber->GetMinimum() + + pSettingNumber->GetStep() * m_pSpin->GetValue())); + + break; + } + + case SettingType::String: + SetValid(std::static_pointer_cast<CSettingString>(m_pSetting) + ->SetValue(m_pSpin->GetStringValue())); + break; + + default: + return false; + } + + return IsValid(); +} + +void CGUIControlSpinExSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (fromControl || m_pSpin == NULL) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + + FillControl(!updateDisplayOnly); + + if (!updateDisplayOnly) + { + // disable the spinner if it has less than two items + if (!m_pSpin->IsDisabled() && (m_pSpin->GetMaximum() - m_pSpin->GetMinimum()) == 0) + m_pSpin->SetEnabled(false); + } +} + +void CGUIControlSpinExSetting::FillControl(bool updateValues) +{ + if (m_pSpin == NULL) + return; + + if (updateValues) + m_pSpin->Clear(); + + const std::string& controlFormat = m_pSetting->GetControl()->GetFormat(); + if (controlFormat == "number") + { + std::shared_ptr<CSettingNumber> pSettingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + m_pSpin->SetFloatValue((float)pSettingNumber->GetValue()); + } + else if (controlFormat == "integer") + FillIntegerSettingControl(updateValues); + else if (controlFormat == "string") + { + if (m_pSetting->GetType() == SettingType::Integer) + FillIntegerSettingControl(updateValues); + else if (m_pSetting->GetType() == SettingType::Number) + FillFloatSettingControl(); + else if (m_pSetting->GetType() == SettingType::String) + FillStringSettingControl(updateValues); + } +} + +void CGUIControlSpinExSetting::FillIntegerSettingControl(bool updateValues) +{ + IntegerSettingOptions options; + std::set<int> selectedValues; + // get the integer options + if (!GetIntegerOptions(m_pSetting, options, selectedValues, m_localizer, updateValues) || + selectedValues.size() != 1) + return; + + if (updateValues) + { + // add them to the spinner + for (const auto& option : options) + m_pSpin->AddLabel(option.label, option.value); + } + + // and set the current value + m_pSpin->SetValue(*selectedValues.begin()); +} + +void CGUIControlSpinExSetting::FillFloatSettingControl() +{ + std::shared_ptr<CSettingNumber> pSettingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + std::shared_ptr<const CSettingControlFormattedRange> control = + std::static_pointer_cast<const CSettingControlFormattedRange>(m_pSetting->GetControl()); + int index = 0; + int currentIndex = 0; + for (double value = pSettingNumber->GetMinimum(); value <= pSettingNumber->GetMaximum(); + value += pSettingNumber->GetStep(), index++) + { + if (value == pSettingNumber->GetValue()) + { + currentIndex = index; + break; + } + } + + m_pSpin->SetValue(currentIndex); +} + +void CGUIControlSpinExSetting::FillStringSettingControl(bool updateValues) +{ + StringSettingOptions options; + std::set<std::string> selectedValues; + // get the string options + if (!GetStringOptions(m_pSetting, options, selectedValues, m_localizer, updateValues) || + selectedValues.size() != 1) + return; + + if (updateValues) + { + // add them to the spinner + for (const auto& option : options) + m_pSpin->AddLabel(option.label, option.value); + } + + // and set the current value + m_pSpin->SetStringValue(*selectedValues.begin()); +} + +CGUIControlListSetting::CGUIControlListSetting(CGUIButtonControl* pButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, std::move(pSetting), localizer) +{ + m_pButton = pButton; + if (m_pButton == NULL) + return; + + m_pButton->SetID(id); +} + +CGUIControlListSetting::~CGUIControlListSetting() = default; + +bool CGUIControlListSetting::OnClick() +{ + if (m_pButton == NULL) + return false; + + CGUIDialogSelect* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>( + WINDOW_DIALOG_SELECT); + if (dialog == NULL) + return false; + + CFileItemList options; + std::shared_ptr<const CSettingControlList> control = + std::static_pointer_cast<const CSettingControlList>(m_pSetting->GetControl()); + bool optionsValid = GetItems(m_pSetting, options, false); + + bool bValueAdded = false; + bool bAllowNewOption = false; + if (m_pSetting->GetType() == SettingType::List) + { + std::shared_ptr<const CSettingList> settingList = + std::static_pointer_cast<const CSettingList>(m_pSetting); + if (settingList->GetElementType() == SettingType::String) + { + bAllowNewOption = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition()) + ->AllowNewOption(); + } + } + if (!bAllowNewOption) + { + // Do not show dialog if + // there are no items to be chosen + if (!optionsValid || options.Size() <= 0) + return false; + + dialog->Reset(); + dialog->SetHeading(CVariant{Localize(m_pSetting->GetLabel())}); + dialog->SetItems(options); + dialog->SetMultiSelection(control->CanMultiSelect()); + dialog->SetUseDetails(control->UseDetails()); + dialog->Open(); + + if (!dialog->IsConfirmed()) + return false; + } + else + { + // Possible to add items, as well as select from any options given + // Add any current values that are not options as items in list + std::vector<CVariant> list = + CSettingUtils::GetList(std::static_pointer_cast<const CSettingList>(m_pSetting)); + for (const auto& value : list) + { + bool found = std::any_of(options.begin(), options.end(), [&](const auto& p) { + return p->GetProperty("value").asString() == value.asString(); + }); + if (!found) + { + CFileItemPtr item(new CFileItem(value.asString())); + item->SetProperty("value", value.asString()); + item->Select(true); + options.Add(item); + } + } + + bool bRepeat = true; + while (bRepeat) + { + std::string strAddButton = Localize(control->GetAddButtonLabel()); + if (strAddButton.empty()) + strAddButton = Localize(15019); // "ADD" + dialog->Reset(); // Clears AddButtonPressed + dialog->SetHeading(CVariant{ Localize(m_pSetting->GetLabel()) }); + dialog->SetItems(options); + dialog->SetMultiSelection(control->CanMultiSelect()); + dialog->EnableButton2(bAllowNewOption, strAddButton); + + dialog->Open(); + + if (!dialog->IsConfirmed()) + return false; + + if (dialog->IsButton2Pressed()) + { + // Get new list value + std::string strLabel; + bool bValidType = false; + while (!bValidType && CGUIKeyboardFactory::ShowAndGetInput( + strLabel, CVariant{ Localize(control->GetAddButtonLabel()) }, false)) + { + // Validate new value is unique and truncate at any comma + StringUtils::Trim(strLabel); + strLabel = strLabel.substr(0, strLabel.find(',')); + if (!strLabel.empty()) + { + bValidType = !std::any_of(options.begin(), options.end(), [&](const auto& p) { + return p->GetProperty("value").asString() == strLabel; + }); + } + if (bValidType) + { // Add new value to the list of options + CFileItemPtr pItem(new CFileItem(strLabel)); + pItem->Select(true); + pItem->SetProperty("value", strLabel); + options.Add(pItem); + bValueAdded = true; + } + } + } + bRepeat = dialog->IsButton2Pressed(); + } + } + + std::vector<CVariant> values; + for (int i : dialog->GetSelectedItems()) + { + const CFileItemPtr item = options.Get(i); + if (item == NULL || !item->HasProperty("value")) + return false; + + values.push_back(item->GetProperty("value")); + } + + bool ret = false; + switch (m_pSetting->GetType()) + { + case SettingType::Integer: + if (values.size() > 1) + return false; + ret = std::static_pointer_cast<CSettingInt>(m_pSetting) + ->SetValue((int)values.at(0).asInteger()); + break; + + case SettingType::String: + if (values.size() > 1) + return false; + ret = std::static_pointer_cast<CSettingString>(m_pSetting)->SetValue(values.at(0).asString()); + break; + + case SettingType::List: + ret = CSettingUtils::SetList(std::static_pointer_cast<CSettingList>(m_pSetting), values); + break; + + default: + return false; + } + + if (ret) + UpdateFromSetting(!bValueAdded); + else + SetValid(false); + + return IsValid(); +} + +void CGUIControlListSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (fromControl || m_pButton == NULL) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + + CFileItemList options; + std::shared_ptr<const CSettingControlList> control = + std::static_pointer_cast<const CSettingControlList>(m_pSetting->GetControl()); + bool optionsValid = GetItems(m_pSetting, options, !updateDisplayOnly); + + bool bAllowNewOption = false; + if (m_pSetting->GetType() == SettingType::List) + { + std::shared_ptr<const CSettingList> settingList = + std::static_pointer_cast<const CSettingList>(m_pSetting); + if (settingList->GetElementType() == SettingType::String) + { + bAllowNewOption = std::static_pointer_cast<const CSettingString>(settingList->GetDefinition()) + ->AllowNewOption(); + } + } + + std::string label2; + if (optionsValid && !control->HideValue()) + { + SettingControlListValueFormatter formatter = control->GetFormatter(); + if (formatter) + label2 = formatter(m_pSetting); + + if (label2.empty() && bAllowNewOption) + { + const std::shared_ptr<const CSettingList> settingList = + std::static_pointer_cast<const CSettingList>(m_pSetting); + label2 = settingList->ToString(); + } + + if (label2.empty()) + { + std::vector<std::string> labels; + for (int index = 0; index < options.Size(); index++) + { + const CFileItemPtr pItem = options.Get(index); + if (pItem->IsSelected()) + labels.push_back(pItem->GetLabel()); + } + + label2 = StringUtils::Join(labels, ", "); + } + } + + m_pButton->SetLabel2(label2); + + if (!updateDisplayOnly) + { + // Disable the control if no items can be added and + // there are no items to be chosen + if (!m_pButton->IsDisabled() && !bAllowNewOption && (options.Size() <= 0)) + m_pButton->SetEnabled(false); + } +} + +bool CGUIControlListSetting::GetItems(const SettingConstPtr& setting, + CFileItemList& items, + bool updateItems) const +{ + std::shared_ptr<const CSettingControlList> control = + std::static_pointer_cast<const CSettingControlList>(setting->GetControl()); + const std::string& controlFormat = control->GetFormat(); + + if (controlFormat == "integer") + return GetIntegerItems(setting, items, updateItems); + else if (controlFormat == "string") + { + if (setting->GetType() == SettingType::Integer || + (setting->GetType() == SettingType::List && + std::static_pointer_cast<const CSettingList>(setting)->GetElementType() == + SettingType::Integer)) + return GetIntegerItems(setting, items, updateItems); + else if (setting->GetType() == SettingType::String || + (setting->GetType() == SettingType::List && + std::static_pointer_cast<const CSettingList>(setting)->GetElementType() == + SettingType::String)) + return GetStringItems(setting, items, updateItems); + } + else + return false; + + return true; +} + +bool CGUIControlListSetting::GetIntegerItems(const SettingConstPtr& setting, + CFileItemList& items, + bool updateItems) const +{ + IntegerSettingOptions options; + std::set<int> selectedValues; + // get the integer options + if (!GetIntegerOptions(setting, options, selectedValues, m_localizer, updateItems)) + return false; + + // turn them into CFileItems and add them to the item list + for (const auto& option : options) + items.Add( + GetFileItem(option.label, option.label2, option.value, option.properties, selectedValues)); + + return true; +} + +bool CGUIControlListSetting::GetStringItems(const SettingConstPtr& setting, + CFileItemList& items, + bool updateItems) const +{ + StringSettingOptions options; + std::set<std::string> selectedValues; + // get the string options + if (!GetStringOptions(setting, options, selectedValues, m_localizer, updateItems)) + return false; + + // turn them into CFileItems and add them to the item list + for (const auto& option : options) + items.Add( + GetFileItem(option.label, option.label2, option.value, option.properties, selectedValues)); + + return true; +} + +CGUIControlButtonSetting::CGUIControlButtonSetting(CGUIButtonControl* pButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, std::move(pSetting), localizer) +{ + m_pButton = pButton; + if (m_pButton == NULL) + return; + + m_pButton->SetID(id); +} + +CGUIControlButtonSetting::~CGUIControlButtonSetting() = default; + +bool CGUIControlButtonSetting::OnClick() +{ + if (m_pButton == NULL) + return false; + + std::shared_ptr<const ISettingControl> control = m_pSetting->GetControl(); + const std::string& controlType = control->GetType(); + const std::string& controlFormat = control->GetFormat(); + if (controlType == "button") + { + std::shared_ptr<const CSettingControlButton> buttonControl = + std::static_pointer_cast<const CSettingControlButton>(control); + if (controlFormat == "addon") + { + // prompt for the addon + std::shared_ptr<CSettingAddon> setting; + std::vector<std::string> addonIDs; + if (m_pSetting->GetType() == SettingType::List) + { + std::shared_ptr<CSettingList> settingList = + std::static_pointer_cast<CSettingList>(m_pSetting); + setting = std::static_pointer_cast<CSettingAddon>(settingList->GetDefinition()); + for (const SettingPtr& addon : settingList->GetValue()) + addonIDs.push_back(std::static_pointer_cast<CSettingAddon>(addon)->GetValue()); + } + else + { + setting = std::static_pointer_cast<CSettingAddon>(m_pSetting); + addonIDs.push_back(setting->GetValue()); + } + + if (CGUIWindowAddonBrowser::SelectAddonID( + setting->GetAddonType(), addonIDs, setting->AllowEmpty(), + buttonControl->ShowAddonDetails(), m_pSetting->GetType() == SettingType::List, + buttonControl->ShowInstalledAddons(), buttonControl->ShowInstallableAddons(), + buttonControl->ShowMoreAddons()) != 1) + return false; + + if (m_pSetting->GetType() == SettingType::List) + std::static_pointer_cast<CSettingList>(m_pSetting)->FromString(addonIDs); + else + SetValid(setting->SetValue(addonIDs[0])); + } + else if (controlFormat == "path" || controlFormat == "file" || controlFormat == "image") + SetValid(GetPath(std::static_pointer_cast<CSettingPath>(m_pSetting), m_localizer)); + else if (controlFormat == "date") + { + std::shared_ptr<CSettingDate> settingDate = + std::static_pointer_cast<CSettingDate>(m_pSetting); + + KODI::TIME::SystemTime systemdate; + settingDate->GetDate().GetAsSystemTime(systemdate); + if (CGUIDialogNumeric::ShowAndGetDate(systemdate, Localize(buttonControl->GetHeading()))) + SetValid(settingDate->SetDate(CDateTime(systemdate))); + } + else if (controlFormat == "time") + { + std::shared_ptr<CSettingTime> settingTime = + std::static_pointer_cast<CSettingTime>(m_pSetting); + + KODI::TIME::SystemTime systemtime; + settingTime->GetTime().GetAsSystemTime(systemtime); + + if (CGUIDialogNumeric::ShowAndGetTime(systemtime, Localize(buttonControl->GetHeading()))) + SetValid(settingTime->SetTime(CDateTime(systemtime))); + } + else if (controlFormat == "action") + { + // simply call the OnSettingAction callback and whoever knows what to + // do can do so (based on the setting's identification) + m_pSetting->OnSettingAction(m_pSetting); + SetValid(true); + } + } + else if (controlType == "slider") + { + float value, min, step, max; + if (m_pSetting->GetType() == SettingType::Integer) + { + std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting); + value = (float)settingInt->GetValue(); + min = (float)settingInt->GetMinimum(); + step = (float)settingInt->GetStep(); + max = (float)settingInt->GetMaximum(); + } + else if (m_pSetting->GetType() == SettingType::Number) + { + std::shared_ptr<CSettingNumber> settingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + value = (float)settingNumber->GetValue(); + min = (float)settingNumber->GetMinimum(); + step = (float)settingNumber->GetStep(); + max = (float)settingNumber->GetMaximum(); + } + else + return false; + + std::shared_ptr<const CSettingControlSlider> sliderControl = + std::static_pointer_cast<const CSettingControlSlider>(control); + CGUIDialogSlider::ShowAndGetInput(Localize(sliderControl->GetHeading()), value, min, step, max, + this, NULL); + SetValid(true); + } + + // update the displayed value + UpdateFromSetting(true); + + return IsValid(); +} + +void CGUIControlButtonSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (fromControl || m_pButton == NULL) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + + std::string strText; + std::shared_ptr<const ISettingControl> control = m_pSetting->GetControl(); + const std::string& controlType = control->GetType(); + const std::string& controlFormat = control->GetFormat(); + + if (controlType == "button") + { + if (!std::static_pointer_cast<const CSettingControlButton>(control)->HideValue()) + { + auto setting = m_pSetting; + if (m_pSetting->GetType() == SettingType::List) + setting = std::static_pointer_cast<CSettingList>(m_pSetting)->GetDefinition(); + + switch (setting->GetType()) + { + case SettingType::String: + { + if (controlFormat == "addon") + { + std::vector<std::string> addonIDs; + if (m_pSetting->GetType() == SettingType::List) + { + for (const auto& addonSetting : + std::static_pointer_cast<CSettingList>(m_pSetting)->GetValue()) + addonIDs.push_back( + std::static_pointer_cast<CSettingAddon>(addonSetting)->GetValue()); + } + else + addonIDs.push_back(std::static_pointer_cast<CSettingString>(setting)->GetValue()); + + std::vector<std::string> addonNames; + for (const auto& addonID : addonIDs) + { + ADDON::AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(addonID, addon, + ADDON::OnlyEnabled::CHOICE_YES)) + addonNames.push_back(addon->Name()); + } + + if (addonNames.empty()) + strText = g_localizeStrings.Get(231); // None + else + strText = StringUtils::Join(addonNames, ", "); + } + else + { + std::string strValue = std::static_pointer_cast<CSettingString>(setting)->GetValue(); + if (controlFormat == "path" || controlFormat == "file" || controlFormat == "image") + { + std::string shortPath; + if (CUtil::MakeShortenPath(strValue, shortPath, 30)) + strText = shortPath; + } + else if (controlFormat == "infolabel") + { + strText = strValue; + if (strText.empty()) + strText = g_localizeStrings.Get(231); // None + } + else + strText = strValue; + } + + break; + } + + case SettingType::Action: + { + // CSettingAction. + // Note: This can be removed once all settings use a proper control & format combination. + // CSettingAction is strictly speaking not designed to have a label2, it does not even have a value. + strText = m_pButton->GetLabel2(); + + break; + } + + default: + break; + } + } + } + else if (controlType == "slider") + { + switch (m_pSetting->GetType()) + { + case SettingType::Integer: + { + std::shared_ptr<const CSettingInt> settingInt = + std::static_pointer_cast<CSettingInt>(m_pSetting); + strText = CGUIControlSliderSetting::GetText(m_pSetting, settingInt->GetValue(), + settingInt->GetMinimum(), settingInt->GetStep(), + settingInt->GetMaximum(), m_localizer); + break; + } + + case SettingType::Number: + { + std::shared_ptr<const CSettingNumber> settingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + strText = CGUIControlSliderSetting::GetText( + m_pSetting, settingNumber->GetValue(), settingNumber->GetMinimum(), + settingNumber->GetStep(), settingNumber->GetMaximum(), m_localizer); + break; + } + + default: + break; + } + } + + m_pButton->SetLabel2(strText); +} + +bool CGUIControlButtonSetting::GetPath(const std::shared_ptr<CSettingPath>& pathSetting, + ILocalizer* localizer) +{ + if (pathSetting == NULL) + return false; + + std::string path = pathSetting->GetValue(); + + VECSOURCES shares; + bool localSharesOnly = false; + const std::vector<std::string>& sources = pathSetting->GetSources(); + for (const auto& source : sources) + { + if (StringUtils::EqualsNoCase(source, "local")) + localSharesOnly = true; + else + { + VECSOURCES* sources = CMediaSourceSettings::GetInstance().GetSources(source); + if (sources != NULL) + shares.insert(shares.end(), sources->begin(), sources->end()); + } + } + + CServiceBroker::GetMediaManager().GetLocalDrives(shares); + if (!localSharesOnly) + CServiceBroker::GetMediaManager().GetNetworkLocations(shares); + + bool result = false; + std::shared_ptr<const CSettingControlButton> control = + std::static_pointer_cast<const CSettingControlButton>(pathSetting->GetControl()); + const auto heading = ::Localize(control->GetHeading(), localizer); + if (control->GetFormat() == "file") + result = CGUIDialogFileBrowser::ShowAndGetFile( + shares, pathSetting->GetMasking(CServiceBroker::GetFileExtensionProvider()), heading, path, + control->UseImageThumbs(), control->UseFileDirectories()); + else if (control->GetFormat() == "image") + { + /* Check setting contains own masking, to filter required image type. + * e.g. png only needed + * <constraints> + * <masking>*.png</masking> + * </constraints> + * <control type="button" format="image"> + * ... + */ + std::string ext = pathSetting->GetMasking(CServiceBroker::GetFileExtensionProvider()); + if (ext.empty()) + ext = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + result = CGUIDialogFileBrowser::ShowAndGetFile(shares, ext, heading, path, true); + } + else + result = + CGUIDialogFileBrowser::ShowAndGetDirectory(shares, heading, path, pathSetting->Writable()); + + if (!result) + return false; + + return pathSetting->SetValue(path); +} + +void CGUIControlButtonSetting::OnSliderChange(void* data, CGUISliderControl* slider) +{ + if (slider == NULL) + return; + + std::string strText; + switch (m_pSetting->GetType()) + { + case SettingType::Integer: + { + std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting); + if (settingInt->SetValue(slider->GetIntValue())) + strText = CGUIControlSliderSetting::GetText(m_pSetting, settingInt->GetValue(), + settingInt->GetMinimum(), settingInt->GetStep(), + settingInt->GetMaximum(), m_localizer); + break; + } + + case SettingType::Number: + { + std::shared_ptr<CSettingNumber> settingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + if (settingNumber->SetValue(static_cast<double>(slider->GetFloatValue()))) + strText = CGUIControlSliderSetting::GetText( + m_pSetting, settingNumber->GetValue(), settingNumber->GetMinimum(), + settingNumber->GetStep(), settingNumber->GetMaximum(), m_localizer); + break; + } + + default: + break; + } + + if (!strText.empty()) + slider->SetTextValue(strText); +} + +CGUIControlEditSetting::CGUIControlEditSetting(CGUIEditControl* pEdit, + int id, + const std::shared_ptr<CSetting>& pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, pSetting, localizer) +{ + std::shared_ptr<const CSettingControlEdit> control = + std::static_pointer_cast<const CSettingControlEdit>(pSetting->GetControl()); + m_pEdit = pEdit; + if (m_pEdit == NULL) + return; + + m_pEdit->SetID(id); + int heading = m_pSetting->GetLabel(); + if (control->GetHeading() > 0) + heading = control->GetHeading(); + if (heading < 0) + heading = 0; + + CGUIEditControl::INPUT_TYPE inputType = CGUIEditControl::INPUT_TYPE_TEXT; + const std::string& controlFormat = control->GetFormat(); + if (controlFormat == "string") + { + if (control->IsHidden()) + inputType = CGUIEditControl::INPUT_TYPE_PASSWORD; + } + else if (controlFormat == "integer" || controlFormat == "number") + { + if (control->VerifyNewValue()) + inputType = CGUIEditControl::INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW; + else + inputType = CGUIEditControl::INPUT_TYPE_NUMBER; + } + else if (controlFormat == "ip") + inputType = CGUIEditControl::INPUT_TYPE_IPADDRESS; + else if (controlFormat == "md5") + inputType = CGUIEditControl::INPUT_TYPE_PASSWORD_MD5; + + m_pEdit->SetInputType(inputType, heading); + + // this will automatically trigger validation so it must be executed after + // having set the value of the control based on the value of the setting + m_pEdit->SetInputValidation(InputValidation, this); +} + +CGUIControlEditSetting::~CGUIControlEditSetting() = default; + +bool CGUIControlEditSetting::OnClick() +{ + if (m_pEdit == NULL) + return false; + + // update our string + if (m_pSetting->GetControl()->GetFormat() == "urlencoded") + { + std::shared_ptr<CSettingUrlEncodedString> urlEncodedSetting = + std::static_pointer_cast<CSettingUrlEncodedString>(m_pSetting); + SetValid(urlEncodedSetting->SetDecodedValue(m_pEdit->GetLabel2())); + } + else + SetValid(m_pSetting->FromString(m_pEdit->GetLabel2())); + + return IsValid(); +} + +void CGUIControlEditSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (fromControl || m_pEdit == NULL) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + + std::shared_ptr<const CSettingControlEdit> control = + std::static_pointer_cast<const CSettingControlEdit>(m_pSetting->GetControl()); + + if (control->GetFormat() == "urlencoded") + { + std::shared_ptr<CSettingUrlEncodedString> urlEncodedSetting = + std::static_pointer_cast<CSettingUrlEncodedString>(m_pSetting); + m_pEdit->SetLabel2(urlEncodedSetting->GetDecodedValue()); + } + else + m_pEdit->SetLabel2(m_pSetting->ToString()); +} + +bool CGUIControlEditSetting::InputValidation(const std::string& input, void* data) +{ + if (data == NULL) + return true; + + CGUIControlEditSetting* editControl = reinterpret_cast<CGUIControlEditSetting*>(data); + if (editControl->GetSetting() == NULL) + return true; + + editControl->SetValid(editControl->GetSetting()->CheckValidity(input)); + return editControl->IsValid(); +} + +CGUIControlSliderSetting::CGUIControlSliderSetting(CGUISettingsSliderControl* pSlider, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, std::move(pSetting), localizer) +{ + m_pSlider = pSlider; + if (m_pSlider == NULL) + return; + + m_pSlider->SetID(id); + + switch (m_pSetting->GetType()) + { + case SettingType::Integer: + { + std::shared_ptr<CSettingInt> settingInt = std::static_pointer_cast<CSettingInt>(m_pSetting); + if (m_pSetting->GetControl()->GetFormat() == "percentage") + m_pSlider->SetType(SLIDER_CONTROL_TYPE_PERCENTAGE); + else + { + m_pSlider->SetType(SLIDER_CONTROL_TYPE_INT); + m_pSlider->SetRange(settingInt->GetMinimum(), settingInt->GetMaximum()); + } + m_pSlider->SetIntInterval(settingInt->GetStep()); + break; + } + + case SettingType::Number: + { + std::shared_ptr<CSettingNumber> settingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT); + m_pSlider->SetFloatRange(static_cast<float>(settingNumber->GetMinimum()), + static_cast<float>(settingNumber->GetMaximum())); + m_pSlider->SetFloatInterval(static_cast<float>(settingNumber->GetStep())); + break; + } + + default: + break; + } +} + +CGUIControlSliderSetting::~CGUIControlSliderSetting() = default; + +bool CGUIControlSliderSetting::OnClick() +{ + if (m_pSlider == NULL) + return false; + + switch (m_pSetting->GetType()) + { + case SettingType::Integer: + SetValid( + std::static_pointer_cast<CSettingInt>(m_pSetting)->SetValue(m_pSlider->GetIntValue())); + break; + + case SettingType::Number: + SetValid(std::static_pointer_cast<CSettingNumber>(m_pSetting) + ->SetValue(static_cast<double>(m_pSlider->GetFloatValue()))); + break; + + default: + return false; + } + + return IsValid(); +} + +void CGUIControlSliderSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (m_pSlider == NULL) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + + std::string strText; + switch (m_pSetting->GetType()) + { + case SettingType::Integer: + { + std::shared_ptr<const CSettingInt> settingInt = + std::static_pointer_cast<CSettingInt>(m_pSetting); + int value; + if (fromControl) + value = m_pSlider->GetIntValue(); + else + { + value = std::static_pointer_cast<CSettingInt>(m_pSetting)->GetValue(); + m_pSlider->SetIntValue(value); + } + + strText = CGUIControlSliderSetting::GetText(m_pSetting, value, settingInt->GetMinimum(), + settingInt->GetStep(), settingInt->GetMaximum(), + m_localizer); + break; + } + + case SettingType::Number: + { + std::shared_ptr<const CSettingNumber> settingNumber = + std::static_pointer_cast<CSettingNumber>(m_pSetting); + double value; + if (fromControl) + value = static_cast<double>(m_pSlider->GetFloatValue()); + else + { + value = std::static_pointer_cast<CSettingNumber>(m_pSetting)->GetValue(); + m_pSlider->SetFloatValue((float)value); + } + + strText = CGUIControlSliderSetting::GetText(m_pSetting, value, settingNumber->GetMinimum(), + settingNumber->GetStep(), + settingNumber->GetMaximum(), m_localizer); + break; + } + + default: + break; + } + + if (!strText.empty()) + m_pSlider->SetTextValue(strText); +} + +std::string CGUIControlSliderSetting::GetText(const std::shared_ptr<CSetting>& setting, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum, + ILocalizer* localizer) +{ + if (setting == NULL || !(value.isInteger() || value.isDouble())) + return ""; + + const auto control = std::static_pointer_cast<const CSettingControlSlider>(setting->GetControl()); + if (control == NULL) + return ""; + + SettingControlSliderFormatter formatter = control->GetFormatter(); + if (formatter != NULL) + return formatter(control, value, minimum, step, maximum); + + std::string formatString = control->GetFormatString(); + if (control->GetFormatLabel() > -1) + formatString = ::Localize(control->GetFormatLabel(), localizer); + + std::string formattedString; + if (FormatText(formatString, value, setting->GetId(), formattedString)) + return formattedString; + + // fall back to default formatting + formatString = control->GetDefaultFormatString(); + if (FormatText(formatString, value, setting->GetId(), formattedString)) + return formattedString; + + return ""; +} + +bool CGUIControlSliderSetting::FormatText(const std::string& formatString, + const CVariant& value, + const std::string& settingId, + std::string& formattedText) +{ + try + { + if (value.isDouble()) + formattedText = StringUtils::Format(formatString, value.asDouble()); + else + formattedText = StringUtils::Format(formatString, static_cast<int>(value.asInteger())); + } + catch (const std::runtime_error& err) + { + CLog::Log(LOGERROR, "Invalid formatting with string \"{}\" for setting \"{}\": {}", + formatString, settingId, err.what()); + return false; + } + + return true; +} + +CGUIControlRangeSetting::CGUIControlRangeSetting(CGUISettingsSliderControl* pSlider, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, std::move(pSetting), localizer) +{ + m_pSlider = pSlider; + if (m_pSlider == NULL) + return; + + m_pSlider->SetID(id); + m_pSlider->SetRangeSelection(true); + + if (m_pSetting->GetType() == SettingType::List) + { + std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting); + SettingConstPtr listDefintion = settingList->GetDefinition(); + switch (listDefintion->GetType()) + { + case SettingType::Integer: + { + std::shared_ptr<const CSettingInt> listDefintionInt = + std::static_pointer_cast<const CSettingInt>(listDefintion); + if (m_pSetting->GetControl()->GetFormat() == "percentage") + m_pSlider->SetType(SLIDER_CONTROL_TYPE_PERCENTAGE); + else + { + m_pSlider->SetType(SLIDER_CONTROL_TYPE_INT); + m_pSlider->SetRange(listDefintionInt->GetMinimum(), listDefintionInt->GetMaximum()); + } + m_pSlider->SetIntInterval(listDefintionInt->GetStep()); + break; + } + + case SettingType::Number: + { + std::shared_ptr<const CSettingNumber> listDefinitionNumber = + std::static_pointer_cast<const CSettingNumber>(listDefintion); + m_pSlider->SetType(SLIDER_CONTROL_TYPE_FLOAT); + m_pSlider->SetFloatRange(static_cast<float>(listDefinitionNumber->GetMinimum()), + static_cast<float>(listDefinitionNumber->GetMaximum())); + m_pSlider->SetFloatInterval(static_cast<float>(listDefinitionNumber->GetStep())); + break; + } + + default: + break; + } + } +} + +CGUIControlRangeSetting::~CGUIControlRangeSetting() = default; + +bool CGUIControlRangeSetting::OnClick() +{ + if (m_pSlider == NULL || m_pSetting->GetType() != SettingType::List) + return false; + + std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting); + const SettingList& settingListValues = settingList->GetValue(); + if (settingListValues.size() != 2) + return false; + + std::vector<CVariant> values; + SettingConstPtr listDefintion = settingList->GetDefinition(); + switch (listDefintion->GetType()) + { + case SettingType::Integer: + values.emplace_back(m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorLower)); + values.emplace_back(m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorUpper)); + break; + + case SettingType::Number: + values.emplace_back(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorLower)); + values.emplace_back(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorUpper)); + break; + + default: + return false; + } + + if (values.size() != 2) + return false; + + SetValid(CSettingUtils::SetList(settingList, values)); + return IsValid(); +} + +void CGUIControlRangeSetting::Update(bool fromControl, bool updateDisplayOnly) +{ + if (m_pSlider == NULL || m_pSetting->GetType() != SettingType::List) + return; + + CGUIControlBaseSetting::Update(fromControl, updateDisplayOnly); + + std::shared_ptr<CSettingList> settingList = std::static_pointer_cast<CSettingList>(m_pSetting); + const SettingList& settingListValues = settingList->GetValue(); + if (settingListValues.size() != 2) + return; + + SettingConstPtr listDefintion = settingList->GetDefinition(); + std::shared_ptr<const CSettingControlRange> controlRange = + std::static_pointer_cast<const CSettingControlRange>(m_pSetting->GetControl()); + const std::string& controlFormat = controlRange->GetFormat(); + + std::string strText; + std::string strTextLower, strTextUpper; + std::string formatString = + Localize(controlRange->GetFormatLabel() > -1 ? controlRange->GetFormatLabel() : 21469); + std::string valueFormat = controlRange->GetValueFormat(); + if (controlRange->GetValueFormatLabel() > -1) + valueFormat = Localize(controlRange->GetValueFormatLabel()); + + switch (listDefintion->GetType()) + { + case SettingType::Integer: + { + int valueLower, valueUpper; + if (fromControl) + { + valueLower = m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorLower); + valueUpper = m_pSlider->GetIntValue(CGUISliderControl::RangeSelectorUpper); + } + else + { + valueLower = std::static_pointer_cast<CSettingInt>(settingListValues[0])->GetValue(); + valueUpper = std::static_pointer_cast<CSettingInt>(settingListValues[1])->GetValue(); + m_pSlider->SetIntValue(valueLower, CGUISliderControl::RangeSelectorLower); + m_pSlider->SetIntValue(valueUpper, CGUISliderControl::RangeSelectorUpper); + } + + if (controlFormat == "date" || controlFormat == "time") + { + CDateTime dateLower((time_t)valueLower); + CDateTime dateUpper((time_t)valueUpper); + + if (controlFormat == "date") + { + if (valueFormat.empty()) + { + strTextLower = dateLower.GetAsLocalizedDate(); + strTextUpper = dateUpper.GetAsLocalizedDate(); + } + else + { + strTextLower = dateLower.GetAsLocalizedDate(valueFormat); + strTextUpper = dateUpper.GetAsLocalizedDate(valueFormat); + } + } + else + { + if (valueFormat.empty()) + valueFormat = "mm:ss"; + + strTextLower = dateLower.GetAsLocalizedTime(valueFormat); + strTextUpper = dateUpper.GetAsLocalizedTime(valueFormat); + } + } + else + { + strTextLower = StringUtils::Format(valueFormat, valueLower); + strTextUpper = StringUtils::Format(valueFormat, valueUpper); + } + + if (valueLower != valueUpper) + strText = StringUtils::Format(formatString, strTextLower, strTextUpper); + else + strText = strTextLower; + break; + } + + case SettingType::Number: + { + double valueLower, valueUpper; + if (fromControl) + { + valueLower = + static_cast<double>(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorLower)); + valueUpper = + static_cast<double>(m_pSlider->GetFloatValue(CGUISliderControl::RangeSelectorUpper)); + } + else + { + valueLower = std::static_pointer_cast<CSettingNumber>(settingListValues[0])->GetValue(); + valueUpper = std::static_pointer_cast<CSettingNumber>(settingListValues[1])->GetValue(); + m_pSlider->SetFloatValue((float)valueLower, CGUISliderControl::RangeSelectorLower); + m_pSlider->SetFloatValue((float)valueUpper, CGUISliderControl::RangeSelectorUpper); + } + + strTextLower = StringUtils::Format(valueFormat, valueLower); + if (valueLower != valueUpper) + { + strTextUpper = StringUtils::Format(valueFormat, valueUpper); + strText = StringUtils::Format(formatString, strTextLower, strTextUpper); + } + else + strText = strTextLower; + break; + } + + default: + strText.clear(); + break; + } + + if (!strText.empty()) + m_pSlider->SetTextValue(strText); +} + +CGUIControlSeparatorSetting::CGUIControlSeparatorSetting(CGUIImage* pImage, + int id, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, NULL, localizer) +{ + m_pImage = pImage; + if (m_pImage == NULL) + return; + + m_pImage->SetID(id); +} + +CGUIControlSeparatorSetting::~CGUIControlSeparatorSetting() = default; + +CGUIControlGroupTitleSetting::CGUIControlGroupTitleSetting(CGUILabelControl* pLabel, + int id, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, NULL, localizer) +{ + m_pLabel = pLabel; + if (m_pLabel == NULL) + return; + + m_pLabel->SetID(id); +} + +CGUIControlGroupTitleSetting::~CGUIControlGroupTitleSetting() = default; + +CGUIControlLabelSetting::CGUIControlLabelSetting(CGUIButtonControl* pButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer) + : CGUIControlBaseSetting(id, std::move(pSetting), localizer) +{ + m_pButton = pButton; + if (m_pButton == NULL) + return; + + m_pButton->SetID(id); + UpdateFromSetting(); +} diff --git a/xbmc/settings/windows/GUIControlSettings.h b/xbmc/settings/windows/GUIControlSettings.h new file mode 100644 index 0000000..777b60d --- /dev/null +++ b/xbmc/settings/windows/GUIControlSettings.h @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/ISliderCallback.h" +#include "utils/ILocalizer.h" + +#include <functional> +#include <memory> +#include <stdlib.h> +#include <string> + +class CGUIControl; +class CGUIImage; +class CGUISpinControlEx; +class CGUIEditControl; +class CGUIButtonControl; +class CGUIRadioButtonControl; +class CGUISettingsSliderControl; +class CGUILabelControl; +class CGUIColorButtonControl; + +class CSetting; +class CSettingControlSlider; +class CSettingString; +class CSettingPath; + +class CFileItemList; +class CVariant; + +class CGUIControlBaseSetting : protected ILocalizer +{ +public: + CGUIControlBaseSetting(int id, std::shared_ptr<CSetting> pSetting, ILocalizer* localizer); + ~CGUIControlBaseSetting() override = default; + + int GetID() const { return m_id; } + std::shared_ptr<CSetting> GetSetting() { return m_pSetting; } + + /*! + \brief Specifies that this setting should update after a delay + Useful for settings that have options to navigate through + and may take a while, or require additional input to update + once the final setting is chosen. Settings default to updating + instantly. + \sa IsDelayed() + */ + void SetDelayed() { m_delayed = true; } + + /*! + \brief Returns whether this setting should have delayed update + \return true if the setting's update should be delayed + \sa SetDelayed() + */ + bool IsDelayed() const { return m_delayed; } + + /*! + \brief Returns whether this setting is enabled or disabled + This state is independent of the real enabled state of a + setting control but represents the enabled state of the + setting itself based on specific conditions. + \return true if the setting is enabled otherwise false + \sa SetEnabled() + */ + bool IsEnabled() const; + + /*! + \brief Returns whether the setting's value is valid or not + */ + bool IsValid() const { return m_valid; } + + void SetValid(bool valid) { m_valid = valid; } + + virtual CGUIControl* GetControl() { return NULL; } + virtual bool OnClick() { return false; } + void UpdateFromControl(); + void UpdateFromSetting(bool updateDisplayOnly = false); + virtual void Clear() = 0; ///< Clears the attached control +protected: + // implementation of ILocalizer + std::string Localize(std::uint32_t code) const override; + + virtual void Update(bool fromControl, bool updateDisplayOnly); + + int m_id; + std::shared_ptr<CSetting> m_pSetting; + ILocalizer* m_localizer; + bool m_delayed; + bool m_valid; +}; + +class CGUIControlRadioButtonSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlRadioButtonSetting(CGUIRadioButtonControl* pRadioButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlRadioButtonSetting() override; + + void Select(bool bSelect); + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pRadioButton); } + bool OnClick() override; + void Clear() override { m_pRadioButton = NULL; } + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + CGUIRadioButtonControl* m_pRadioButton; +}; + +class CGUIControlColorButtonSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlColorButtonSetting(CGUIColorButtonControl* pColorControl, + int id, + const std::shared_ptr<CSetting>& pSetting, + ILocalizer* localizer); + ~CGUIControlColorButtonSetting() override; + + void Select(bool bSelect); + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pColorButton); } + bool OnClick() override; + void Clear() override { m_pColorButton = nullptr; } + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + CGUIColorButtonControl* m_pColorButton; +}; + +class CGUIControlSpinExSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlSpinExSetting(CGUISpinControlEx* pSpin, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlSpinExSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pSpin); } + bool OnClick() override; + void Clear() override { m_pSpin = NULL; } + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + void FillControl(bool updateDisplayOnly); + void FillIntegerSettingControl(bool updateValues); + void FillFloatSettingControl(); + void FillStringSettingControl(bool updateValues); + CGUISpinControlEx* m_pSpin; +}; + +class CGUIControlListSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlListSetting(CGUIButtonControl* pButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlListSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); } + bool OnClick() override; + void Clear() override { m_pButton = NULL; } + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + bool GetItems(const std::shared_ptr<const CSetting>& setting, + CFileItemList& items, + bool updateItems) const; + bool GetIntegerItems(const std::shared_ptr<const CSetting>& setting, + CFileItemList& items, + bool updateItems) const; + bool GetStringItems(const std::shared_ptr<const CSetting>& setting, + CFileItemList& items, + bool updateItems) const; + + CGUIButtonControl* m_pButton; +}; + +class CGUIControlListColorSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlListColorSetting(CGUIButtonControl* pButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlListColorSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); } + bool OnClick() override; + void Clear() override { m_pButton = nullptr; } + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + CGUIButtonControl* m_pButton; +}; + +class CGUIControlButtonSetting : public CGUIControlBaseSetting, protected ISliderCallback +{ +public: + CGUIControlButtonSetting(CGUIButtonControl* pButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlButtonSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); } + bool OnClick() override; + void Clear() override { m_pButton = NULL; } + + static bool GetPath(const std::shared_ptr<CSettingPath>& pathSetting, ILocalizer* localizer); + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + + // implementations of ISliderCallback + void OnSliderChange(void* data, CGUISliderControl* slider) override; + +private: + CGUIButtonControl* m_pButton; +}; + +class CGUIControlEditSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlEditSetting(CGUIEditControl* pButton, + int id, + const std::shared_ptr<CSetting>& pSetting, + ILocalizer* localizer); + ~CGUIControlEditSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pEdit); } + bool OnClick() override; + void Clear() override { m_pEdit = NULL; } + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + static bool InputValidation(const std::string& input, void* data); + + CGUIEditControl* m_pEdit; +}; + +class CGUIControlSliderSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlSliderSetting(CGUISettingsSliderControl* pSlider, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlSliderSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pSlider); } + bool OnClick() override; + void Clear() override { m_pSlider = NULL; } + + static std::string GetText(const std::shared_ptr<CSetting>& setting, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum, + ILocalizer* localizer); + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + static bool FormatText(const std::string& formatString, + const CVariant& value, + const std::string& settingId, + std::string& formattedText); + + CGUISettingsSliderControl* m_pSlider; +}; + +class CGUIControlRangeSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlRangeSetting(CGUISettingsSliderControl* pSlider, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlRangeSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pSlider); } + bool OnClick() override; + void Clear() override { m_pSlider = NULL; } + +protected: + // specialization of CGUIControlBaseSetting + void Update(bool fromControl, bool updateDisplayOnly) override; + +private: + CGUISettingsSliderControl* m_pSlider; +}; + +class CGUIControlSeparatorSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlSeparatorSetting(CGUIImage* pImage, int id, ILocalizer* localizer); + ~CGUIControlSeparatorSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pImage); } + bool OnClick() override { return false; } + void Clear() override { m_pImage = NULL; } + +private: + CGUIImage* m_pImage; +}; + +class CGUIControlGroupTitleSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlGroupTitleSetting(CGUILabelControl* pLabel, int id, ILocalizer* localizer); + ~CGUIControlGroupTitleSetting() override; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pLabel); } + bool OnClick() override { return false; } + void Clear() override { m_pLabel = NULL; } + +private: + CGUILabelControl* m_pLabel; +}; + +class CGUIControlLabelSetting : public CGUIControlBaseSetting +{ +public: + CGUIControlLabelSetting(CGUIButtonControl* pButton, + int id, + std::shared_ptr<CSetting> pSetting, + ILocalizer* localizer); + ~CGUIControlLabelSetting() override = default; + + CGUIControl* GetControl() override { return reinterpret_cast<CGUIControl*>(m_pButton); } + void Clear() override { m_pButton = NULL; } + +private: + CGUIButtonControl* m_pButton; +}; diff --git a/xbmc/settings/windows/GUIWindowSettings.cpp b/xbmc/settings/windows/GUIWindowSettings.cpp new file mode 100644 index 0000000..d8edede --- /dev/null +++ b/xbmc/settings/windows/GUIWindowSettings.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowSettings.h" + +#include "guilib/WindowIDs.h" + +CGUIWindowSettings::CGUIWindowSettings(void) + : CGUIWindow(WINDOW_SETTINGS_MENU, "Settings.xml") +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIWindowSettings::~CGUIWindowSettings(void) = default; diff --git a/xbmc/settings/windows/GUIWindowSettings.h b/xbmc/settings/windows/GUIWindowSettings.h new file mode 100644 index 0000000..c658186 --- /dev/null +++ b/xbmc/settings/windows/GUIWindowSettings.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIWindow.h" + +class CGUIWindowSettings : + public CGUIWindow +{ +public: + CGUIWindowSettings(void); + ~CGUIWindowSettings(void) override; +}; diff --git a/xbmc/settings/windows/GUIWindowSettingsCategory.cpp b/xbmc/settings/windows/GUIWindowSettingsCategory.cpp new file mode 100644 index 0000000..18be9cd --- /dev/null +++ b/xbmc/settings/windows/GUIWindowSettingsCategory.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowSettingsCategory.h" + +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "input/Key.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/SettingSection.h" +#include "settings/windows/GUIControlSettings.h" +#include "utils/log.h" +#include "view/ViewStateSettings.h" + +#include <string> + +#define SETTINGS_SYSTEM WINDOW_SETTINGS_SYSTEM - WINDOW_SETTINGS_START +#define SETTINGS_SERVICE WINDOW_SETTINGS_SERVICE - WINDOW_SETTINGS_START +#define SETTINGS_PVR WINDOW_SETTINGS_MYPVR - WINDOW_SETTINGS_START +#define SETTINGS_PLAYER WINDOW_SETTINGS_PLAYER - WINDOW_SETTINGS_START +#define SETTINGS_MEDIA WINDOW_SETTINGS_MEDIA - WINDOW_SETTINGS_START +#define SETTINGS_INTERFACE WINDOW_SETTINGS_INTERFACE - WINDOW_SETTINGS_START +#define SETTINGS_GAMES WINDOW_SETTINGS_MYGAMES - WINDOW_SETTINGS_START + +#define CONTROL_BTN_LEVELS 20 + +typedef struct { + int id; + std::string name; +} SettingGroup; + +static const SettingGroup s_settingGroupMap[] = { { SETTINGS_SYSTEM, "system" }, + { SETTINGS_SERVICE, "services" }, + { SETTINGS_PVR, "pvr" }, + { SETTINGS_PLAYER, "player" }, + { SETTINGS_MEDIA, "media" }, + { SETTINGS_INTERFACE, "interface" }, + { SETTINGS_GAMES, "games" } }; + +#define SettingGroupSize sizeof(s_settingGroupMap) / sizeof(SettingGroup) + +CGUIWindowSettingsCategory::CGUIWindowSettingsCategory() + : CGUIDialogSettingsManagerBase(WINDOW_SETTINGS_SYSTEM, "SettingsCategory.xml"), + m_settings(CServiceBroker::GetSettingsComponent()->GetSettings()) +{ + // set the correct ID range... + m_idRange.clear(); + m_idRange.push_back(WINDOW_SETTINGS_SYSTEM); + m_idRange.push_back(WINDOW_SETTINGS_SERVICE); + m_idRange.push_back(WINDOW_SETTINGS_MYPVR); + m_idRange.push_back(WINDOW_SETTINGS_PLAYER); + m_idRange.push_back(WINDOW_SETTINGS_MEDIA); + m_idRange.push_back(WINDOW_SETTINGS_INTERFACE); + m_idRange.push_back(WINDOW_SETTINGS_MYGAMES); +} + +CGUIWindowSettingsCategory::~CGUIWindowSettingsCategory() = default; + +bool CGUIWindowSettingsCategory::OnMessage(CGUIMessage &message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + m_iSection = message.GetParam2() - CGUIDialogSettingsManagerBase::GetID(); + CGUIDialogSettingsManagerBase::OnMessage(message); + m_returningFromSkinLoad = false; + + if (!message.GetStringParam(0).empty()) + FocusElement(message.GetStringParam(0)); + + return true; + } + + case GUI_MSG_FOCUSED: + { + if (!m_returningFromSkinLoad) + CGUIDialogSettingsManagerBase::OnMessage(message); + return true; + } + + case GUI_MSG_LOAD_SKIN: + { + if (IsActive()) + m_returningFromSkinLoad = true; + break; + } + + case GUI_MSG_NOTIFY_ALL: + { + if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE) + { + if (IsActive() && CDisplaySettings::GetInstance().GetCurrentResolution() != CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution()) + { + CDisplaySettings::GetInstance().SetCurrentResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(), true); + CreateSettings(); + } + } + break; + } + + case GUI_MSG_PLAYBACK_STARTED: + case GUI_MSG_PLAYBACK_ENDED: + case GUI_MSG_PLAYBACK_STOPPED: + { + if (IsActive()) + { + UpdateSettings(); + } + break; + } + } + + return CGUIDialogSettingsManagerBase::OnMessage(message); +} + +bool CGUIWindowSettingsCategory::OnAction(const CAction &action) +{ + switch (action.GetID()) + { + case ACTION_SETTINGS_LEVEL_CHANGE: + { + //Test if we can access the new level + if (!g_passwordManager.CheckSettingLevelLock(CViewStateSettings::GetInstance().GetNextSettingLevel(), true)) + return false; + + CViewStateSettings::GetInstance().CycleSettingLevel(); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + // try to keep the current position + std::string oldCategory; + if (m_iCategory >= 0 && m_iCategory < (int)m_categories.size()) + oldCategory = m_categories[m_iCategory]->GetId(); + + SET_CONTROL_LABEL(CONTROL_BTN_LEVELS, 10036 + (int)CViewStateSettings::GetInstance().GetSettingLevel()); + // only re-create the categories, the settings will be created later + SetupControls(false); + + m_iCategory = 0; + // try to find the category that was previously selected + if (!oldCategory.empty()) + { + for (int i = 0; i < (int)m_categories.size(); i++) + { + if (m_categories[i]->GetId() == oldCategory) + { + m_iCategory = i; + break; + } + } + } + + CreateSettings(); + return true; + } + + default: + break; + } + + return CGUIDialogSettingsManagerBase::OnAction(action); +} + +bool CGUIWindowSettingsCategory::OnBack(int actionID) +{ + Save(); + return CGUIDialogSettingsManagerBase::OnBack(actionID); +} + +void CGUIWindowSettingsCategory::OnWindowLoaded() +{ + SET_CONTROL_LABEL(CONTROL_BTN_LEVELS, 10036 + (int)CViewStateSettings::GetInstance().GetSettingLevel()); + CGUIDialogSettingsManagerBase::OnWindowLoaded(); +} + +int CGUIWindowSettingsCategory::GetSettingLevel() const +{ + return (int)CViewStateSettings::GetInstance().GetSettingLevel(); +} + +SettingSectionPtr CGUIWindowSettingsCategory::GetSection() +{ + for (const SettingGroup& settingGroup : s_settingGroupMap) + { + if (settingGroup.id == m_iSection) + return m_settings->GetSection(settingGroup.name); + } + + return NULL; +} + +bool CGUIWindowSettingsCategory::Save() +{ + m_settings->Save(); + + return true; +} + +CSettingsManager* CGUIWindowSettingsCategory::GetSettingsManager() const +{ + return m_settings->GetSettingsManager(); +} + +void CGUIWindowSettingsCategory::FocusElement(const std::string& elementId) +{ + for (size_t i = 0; i < m_categories.size(); ++i) + { + if (m_categories[i]->GetId() == elementId) + { + SET_CONTROL_FOCUS(CONTROL_SETTINGS_START_BUTTONS + i, 0); + return; + } + for (const auto& group: m_categories[i]->GetGroups()) + { + for (const auto& setting : group->GetSettings()) + { + if (setting->GetId() == elementId) + { + SET_CONTROL_FOCUS(CONTROL_SETTINGS_START_BUTTONS + i, 0); + + auto control = GetSettingControl(elementId); + if (control) + SET_CONTROL_FOCUS(control->GetID(), 0); + else + CLog::Log(LOGERROR, + "CGUIWindowSettingsCategory: failed to get control for setting '{}'.", + elementId); + return; + } + } + } + } + CLog::Log(LOGERROR, + "CGUIWindowSettingsCategory: failed to set focus. unknown category/setting id '{}'.", + elementId); +} diff --git a/xbmc/settings/windows/GUIWindowSettingsCategory.h b/xbmc/settings/windows/GUIWindowSettingsCategory.h new file mode 100644 index 0000000..88c3ac8 --- /dev/null +++ b/xbmc/settings/windows/GUIWindowSettingsCategory.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "settings/dialogs/GUIDialogSettingsManagerBase.h" + +class CSettings; + +class CGUIWindowSettingsCategory : public CGUIDialogSettingsManagerBase +{ +public: + CGUIWindowSettingsCategory(); + ~CGUIWindowSettingsCategory() override; + + // specialization of CGUIControl + bool OnMessage(CGUIMessage &message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + int GetID() const override { return CGUIDialogSettingsManagerBase::GetID() + m_iSection; } + + // specialization of CGUIWindow + bool IsDialog() const override { return false; } + +protected: + // specialization of CGUIWindow + void OnWindowLoaded() override; + + // implementation of CGUIDialogSettingsBase + int GetSettingLevel() const override; + std::shared_ptr<CSettingSection> GetSection() override; + bool Save() override; + + // implementation of CGUIDialogSettingsManagerBase + CSettingsManager* GetSettingsManager() const override; + + /*! + * Set focus to a category or setting in this window. The setting/category must be active in the + * current level. + */ + void FocusElement(const std::string& elementId); + + std::shared_ptr<CSettings> m_settings; + int m_iSection = 0; + bool m_returningFromSkinLoad = false; // true if we are returning from loading the skin +}; diff --git a/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp new file mode 100644 index 0000000..7e67de6 --- /dev/null +++ b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.cpp @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowSettingsScreenCalibration.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMoverControl.h" +#include "guilib/GUIResizeControl.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "settings/DisplaySettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/SubtitlesSettings.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "windowing/WinSystem.h" + +#include <string> +#include <utility> + +using namespace KODI; + +namespace +{ +constexpr int CONTROL_LABEL_RES = 2; +constexpr int CONTROL_LABEL_DESCRIPTION = 3; +constexpr int CONTROL_LABEL_VALUE = 4; +constexpr int CONTROL_TOP_LEFT = 8; +constexpr int CONTROL_BOTTOM_RIGHT = 9; +constexpr int CONTROL_SUBTITLES = 10; +constexpr int CONTROL_PIXEL_RATIO = 11; +constexpr int CONTROL_RESET = 12; +constexpr int CONTROL_VIDEO = 20; + +constexpr int DEFAULT_GUI_HEIGHT = 1080; +constexpr int DEFAULT_GUI_WIDTH = 1920; + +// Fixed transparent space of the subtitle bar (on top + below) for touch screen +// must match with the space of the skin bar image +constexpr int CONTROL_SUBTITLES_SPACE = 80; +} // unnamed namespace + +CGUIWindowSettingsScreenCalibration::CGUIWindowSettingsScreenCalibration(void) + : CGUIWindow(WINDOW_SCREEN_CALIBRATION, "SettingsScreenCalibration.xml") +{ + m_iCurRes = 0; + m_iControl = 0; + m_fPixelRatioBoxHeight = 0.0f; + m_needsScaling = false; // we handle all the scaling +} + +CGUIWindowSettingsScreenCalibration::~CGUIWindowSettingsScreenCalibration(void) = default; + + +void CGUIWindowSettingsScreenCalibration::ResetCalibration() +{ + // We ask to reset the calibration + // Reset will be applied to: windowed mode or per fullscreen resolution + CGUIDialogYesNo* pDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>(WINDOW_DIALOG_YES_NO); + pDialog->SetHeading(CVariant{20325}); + std::string strText = StringUtils::Format( + g_localizeStrings.Get(20326), + CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(m_Res[m_iCurRes]).strMode); + pDialog->SetText(CVariant{std::move(strText)}); + pDialog->SetChoice(0, CVariant{222}); + pDialog->SetChoice(1, CVariant{186}); + pDialog->Open(); + if (pDialog->IsConfirmed()) + { + CServiceBroker::GetWinSystem()->GetGfxContext().ResetScreenParameters(m_Res[m_iCurRes]); + ResetControls(); + // Send GUI_MSG_WINDOW_RESIZE to rescale font size/aspect for label controls + CServiceBroker::GetGUI()->GetWindowManager().SendMessage( + GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE); + } +} + +bool CGUIWindowSettingsScreenCalibration::OnAction(const CAction& action) +{ + switch (action.GetID()) + { + case ACTION_CALIBRATE_SWAP_ARROWS: + { + NextControl(); + return true; + } + break; + + case ACTION_CALIBRATE_RESET: + { + ResetCalibration(); + return true; + } + break; + + case ACTION_CHANGE_RESOLUTION: + // choose the next resolution in our list + { + m_iCurRes = (m_iCurRes + 1) % m_Res.size(); + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(m_Res[m_iCurRes], false); + ResetControls(); + // Send GUI_MSG_WINDOW_RESIZE to rescale font size/aspect for label controls + CServiceBroker::GetGUI()->GetWindowManager().SendMessage( + GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE); + return true; + } + break; + + // ignore all gesture meta actions + case ACTION_GESTURE_BEGIN: + case ACTION_GESTURE_END: + case ACTION_GESTURE_ABORT: + case ACTION_GESTURE_NOTIFY: + case ACTION_GESTURE_PAN: + case ACTION_GESTURE_ROTATE: + case ACTION_GESTURE_ZOOM: + return true; + + case ACTION_MOUSE_LEFT_CLICK: + case ACTION_TOUCH_TAP: + if (GetFocusedControlID() == CONTROL_RESET) + { + ResetCalibration(); + return true; + } + break; + } + + // if we see a mouse move event without dx and dy (amount2 and amount3) these + // are the focus actions which are generated on touch events and those should + // be eaten/ignored here. Else we will switch to the screencalibration controls + // which are at that x/y value on each touch/tap/swipe which makes the whole window + // unusable for touch screens + if (action.GetID() == ACTION_MOUSE_MOVE && action.GetAmount(2) == 0 && action.GetAmount(3) == 0) + return true; + + return CGUIWindow::OnAction(action); // base class to handle basic movement etc. +} + +void CGUIWindowSettingsScreenCalibration::AllocResources(bool forceLoad) +{ + CGUIWindow::AllocResources(forceLoad); +} + +void CGUIWindowSettingsScreenCalibration::FreeResources(bool forceUnload) +{ + CGUIWindow::FreeResources(forceUnload); +} + + +bool CGUIWindowSettingsScreenCalibration::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + CDisplaySettings::GetInstance().UpdateCalibrations(); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + CServiceBroker::GetWinSystem()->GetGfxContext().SetCalibrating(false); + // reset our screen resolution to what it was initially + CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution( + CDisplaySettings::GetInstance().GetCurrentResolution(), false); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage( + GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + CGUIWindow::OnMessage(message); + CServiceBroker::GetWinSystem()->GetGfxContext().SetCalibrating(true); + + // Get the default XML size values of controls, + // we will use these values to scale controls when the resolution change + for (int id = CONTROL_TOP_LEFT; id <= CONTROL_RESET; id++) + { + CGUIControl* control = GetControl(id); + if (control) + { + m_controlsSize.emplace(id, std::make_pair(control->GetHeight(), control->GetWidth())); + } + } + + // Get the allowable resolutions that we can calibrate... + m_Res.clear(); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + bool isPlayingVideo{appPlayer->IsPlayingVideo()}; + if (isPlayingVideo) + { // don't allow resolution switching if we are playing a video + + appPlayer->TriggerUpdateResolution(); + + m_iCurRes = 0; + m_Res.push_back(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution()); + SET_CONTROL_VISIBLE(CONTROL_VIDEO); + } + else + { + SET_CONTROL_HIDDEN(CONTROL_VIDEO); + CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(m_Res); + // find our starting resolution + m_iCurRes = FindCurrentResolution(); + } + + // Setup the first control + m_iControl = CONTROL_TOP_LEFT; + + m_isSubtitleBarEnabled = + !(CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetAlignment() != + SUBTITLES::Align::MANUAL && + isPlayingVideo); + + ResetControls(); + return true; + } + break; + case GUI_MSG_CLICKED: + { + // On click event select the next control + NextControl(); + } + break; + case GUI_MSG_NOTIFY_ALL: + { + if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE && + message.GetSenderId() != WINDOW_SCREEN_CALIBRATION && IsActive()) + { + m_Res.clear(); + CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions(m_Res); + m_iCurRes = FindCurrentResolution(); + ResetControls(); + } + } + break; + // send before touch for requesting gesture features - we don't want this + // it would result in unfocus in the onmessage below ... + case GUI_MSG_GESTURE_NOTIFY: + // send after touch for unfocussing - we don't want this in this window! + case GUI_MSG_UNFOCUS_ALL: + return true; + break; + } + return CGUIWindow::OnMessage(message); +} + +unsigned int CGUIWindowSettingsScreenCalibration::FindCurrentResolution() +{ + RESOLUTION curRes = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(); + for (size_t i = 0; i < m_Res.size(); i++) + { + // If it's a CUSTOM (monitor) resolution, then CServiceBroker::GetWinSystem()->GetGfxContext().GetAllowedResolutions() + // returns just one entry with CUSTOM in it. Update that entry to point to the current + // CUSTOM resolution. + if (curRes >= RES_CUSTOM) + { + if (m_Res[i] == RES_CUSTOM) + { + m_Res[i] = curRes; + return i; + } + } + else if (m_Res[i] == CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution()) + return i; + } + CLog::Log(LOGERROR, "CALIBRATION: Reported current resolution: {}", + CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution()); + CLog::Log(LOGERROR, + "CALIBRATION: Could not determine current resolution, falling back to default"); + return 0; +} + +void CGUIWindowSettingsScreenCalibration::NextControl() +{ // set the old control invisible and not focused, and choose the next control + CGUIControl* pControl = GetControl(m_iControl); + if (pControl) + { + pControl->SetVisible(false); + pControl->SetFocus(false); + } + // If the current control is the reset button + // ask to reset the calibration settings + if (m_iControl == CONTROL_RESET) + { + ResetCalibration(); + } + // switch to the next control + m_iControl++; + if (m_iControl > CONTROL_RESET) + m_iControl = CONTROL_TOP_LEFT; + // enable the new control + EnableControl(m_iControl); +} + +void CGUIWindowSettingsScreenCalibration::EnableControl(int iControl) +{ + SET_CONTROL_VISIBLE(CONTROL_TOP_LEFT); + SET_CONTROL_VISIBLE(CONTROL_BOTTOM_RIGHT); + SET_CONTROL_VISIBLE(CONTROL_SUBTITLES); + SET_CONTROL_VISIBLE(CONTROL_PIXEL_RATIO); + SET_CONTROL_VISIBLE(CONTROL_RESET); + SET_CONTROL_FOCUS(iControl, 0); +} + +void CGUIWindowSettingsScreenCalibration::ResetControls() +{ + // disable the video control, so that our other controls take mouse clicks etc. + CONTROL_DISABLE(CONTROL_VIDEO); + // disable the UI calibration for our controls + // and set their limits + // also, set them to invisible if they don't have focus + CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_TOP_LEFT)); + RESOLUTION_INFO info = + CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(m_Res[m_iCurRes]); + + m_subtitleVerticalMargin = static_cast<int>( + static_cast<float>(info.iHeight) / 100 * + CServiceBroker::GetSettingsComponent()->GetSubtitlesSettings()->GetVerticalMarginPerc()); + + if (pControl) + { + pControl->SetLimits(-info.iWidth / 4, -info.iHeight / 4, info.iWidth / 4, info.iHeight / 4); + auto& size = m_controlsSize[CONTROL_TOP_LEFT]; + pControl->SetHeight(size.first / DEFAULT_GUI_HEIGHT * info.iHeight); + pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth); + pControl->SetPosition(static_cast<float>(info.Overscan.left), + static_cast<float>(info.Overscan.top)); + pControl->SetLocation(info.Overscan.left, info.Overscan.top, false); + } + pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_BOTTOM_RIGHT)); + if (pControl) + { + pControl->SetLimits(info.iWidth * 3 / 4, info.iHeight * 3 / 4, info.iWidth * 5 / 4, + info.iHeight * 5 / 4); + auto& size = m_controlsSize[CONTROL_BOTTOM_RIGHT]; + pControl->SetHeight(size.first / DEFAULT_GUI_HEIGHT * info.iHeight); + pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth); + pControl->SetPosition(static_cast<float>(info.Overscan.right) - pControl->GetWidth(), + static_cast<float>(info.Overscan.bottom) - pControl->GetHeight()); + pControl->SetLocation(info.Overscan.right, info.Overscan.bottom, false); + } + // Subtitles and OSD controls can only move up and down + pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_SUBTITLES)); + if (pControl) + { + auto& size = m_controlsSize[CONTROL_SUBTITLES]; + float scaledHeight = size.first / DEFAULT_GUI_HEIGHT * info.iHeight; + float scaledSpace = + static_cast<float>(CONTROL_SUBTITLES_SPACE) / DEFAULT_GUI_HEIGHT * info.iHeight; + m_subtitlesHalfSpace = static_cast<int>(scaledSpace / 2); + int barHeight = static_cast<int>(scaledHeight - scaledSpace); + pControl->SetLimits(0, m_subtitlesHalfSpace + barHeight + info.Overscan.top, 0, + info.Overscan.bottom + m_subtitlesHalfSpace); + pControl->SetHeight(scaledHeight); + pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth); + // If the vertical margin has been changed from the previous calibration, + // the text bar could appear offscreen, then force move to visible area + if (info.iSubtitles - m_subtitleVerticalMargin > info.iHeight) + info.iSubtitles = info.Overscan.bottom; + // We want the text to be at the base of the bar, + // then we shift the position to include the vertical margin + pControl->SetPosition((info.iWidth - pControl->GetWidth()) * 0.5f, + info.iSubtitles - pControl->GetHeight() + m_subtitlesHalfSpace - + m_subtitleVerticalMargin); + pControl->SetLocation(0, info.iSubtitles + m_subtitlesHalfSpace - m_subtitleVerticalMargin, + false); + pControl->SetEnabled(m_isSubtitleBarEnabled); + } + // The pixel ratio control + CGUIResizeControl* pResize = dynamic_cast<CGUIResizeControl*>(GetControl(CONTROL_PIXEL_RATIO)); + if (pResize) + { + pResize->SetLimits(info.iWidth * 0.25f, info.iHeight * 0.5f, info.iWidth * 0.75f, + info.iHeight * 0.5f); + pResize->SetHeight(info.iHeight * 0.5f); + pResize->SetWidth(pResize->GetHeight() / info.fPixelRatio); + pResize->SetPosition((info.iWidth - pResize->GetWidth()) / 2, + (info.iHeight - pResize->GetHeight()) / 2); + } + // The calibration reset + pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_RESET)); + if (pControl) + { + auto& size = m_controlsSize[CONTROL_RESET]; + pControl->SetHeight(size.first / DEFAULT_GUI_HEIGHT * info.iHeight); + pControl->SetWidth(size.second / DEFAULT_GUI_WIDTH * info.iWidth); + float posX = 0; + float posY = info.iHeight - pControl->GetHeight(); + pControl->SetLimits(posX, posY, posX, posY); + pControl->SetPosition(posX, posY); + pControl->SetLocation(posX, posY, false); + } + // Enable the default control + EnableControl(m_iControl); +} + +bool CGUIWindowSettingsScreenCalibration::UpdateFromControl(int iControl) +{ + RESOLUTION_INFO info = + CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(m_Res[m_iCurRes]); + RESOLUTION_INFO infoPrev = info; + std::string labelDescription; + std::string labelValue; + + if (iControl == CONTROL_PIXEL_RATIO) + { + CGUIControl* pControl = GetControl(CONTROL_PIXEL_RATIO); + if (pControl) + { + float fWidth = pControl->GetWidth(); + float fHeight = pControl->GetHeight(); + info.fPixelRatio = fHeight / fWidth; + // recenter our control... + pControl->SetPosition((static_cast<float>(info.iWidth) - pControl->GetWidth()) / 2, + (static_cast<float>(info.iHeight) - pControl->GetHeight()) / 2); + labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(272), + g_localizeStrings.Get(273)); + labelValue = StringUtils::Format("{:5.3f}", info.fPixelRatio); + labelValue = StringUtils::Format(g_localizeStrings.Get(20327), labelValue); + } + } + else + { + CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(iControl)); + if (pControl) + { + switch (iControl) + { + case CONTROL_TOP_LEFT: + { + info.Overscan.left = pControl->GetXLocation(); + info.Overscan.top = pControl->GetYLocation(); + labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(274), + g_localizeStrings.Get(276)); + labelValue = + StringUtils::Format("{}, {}", pControl->GetXLocation(), pControl->GetYLocation()); + labelValue = StringUtils::Format(g_localizeStrings.Get(20327), labelValue); + // Update reset control position + CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_RESET)); + if (pControl) + { + float posX = info.Overscan.left; + float posY = info.Overscan.bottom - pControl->GetHeight(); + pControl->SetLimits(posX, posY, posX, posY); + pControl->SetPosition(posX, posY); + pControl->SetLocation(posX, posY, false); + } + } + break; + + case CONTROL_BOTTOM_RIGHT: + { + info.Overscan.right = pControl->GetXLocation(); + info.Overscan.bottom = pControl->GetYLocation(); + int iXOff1 = info.iWidth - pControl->GetXLocation(); + int iYOff1 = info.iHeight - pControl->GetYLocation(); + labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(275), + g_localizeStrings.Get(276)); + labelValue = StringUtils::Format("{}, {}", iXOff1, iYOff1); + labelValue = StringUtils::Format(g_localizeStrings.Get(20327), labelValue); + // Update reset control position + pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_RESET)); + if (pControl) + { + float posX = info.Overscan.left; + float posY = info.Overscan.bottom - pControl->GetHeight(); + pControl->SetLimits(posX, posY, posX, posY); + pControl->SetPosition(posX, posY); + pControl->SetLocation(posX, posY, false); + } + } + break; + + case CONTROL_SUBTITLES: + { + if (m_isSubtitleBarEnabled) + { + info.iSubtitles = + pControl->GetYLocation() - m_subtitlesHalfSpace + m_subtitleVerticalMargin; + + labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(277), + g_localizeStrings.Get(278)); + labelValue = StringUtils::Format(g_localizeStrings.Get(39184), info.iSubtitles, + info.iSubtitles - m_subtitleVerticalMargin); + } + else + { + labelDescription = StringUtils::Format("[B]{}[/B][CR]{}", g_localizeStrings.Get(277), + g_localizeStrings.Get(39189)); + } + } + break; + + case CONTROL_RESET: + { + labelDescription = g_localizeStrings.Get(20325); + } + break; + } + } + } + + SET_CONTROL_LABEL(CONTROL_LABEL_DESCRIPTION, labelDescription); + SET_CONTROL_LABEL(CONTROL_LABEL_VALUE, labelValue); + + // Set resolution info text + std::string resInfo; + if (CServiceBroker::GetWinSystem()->IsFullScreen()) + { + resInfo = + StringUtils::Format("{} {}x{}@{:.2f} - {}", g_localizeStrings.Get(13287), info.iScreenWidth, + info.iScreenHeight, info.fRefreshRate, g_localizeStrings.Get(244)); + } + else + { + resInfo = StringUtils::Format("{} {}x{} - {}", g_localizeStrings.Get(13287), info.iScreenWidth, + info.iScreenHeight, g_localizeStrings.Get(242)); + } + SET_CONTROL_LABEL(CONTROL_LABEL_RES, resInfo); + + // Detect overscan changes + bool isOverscanChanged = info.Overscan != infoPrev.Overscan; + + // Adjust subtitle bar position due to overscan changes + if (isOverscanChanged) + { + CGUIMoverControl* pControl = dynamic_cast<CGUIMoverControl*>(GetControl(CONTROL_SUBTITLES)); + if (pControl) + { + // Keep the subtitle bar within the overscan boundary + if (info.Overscan.bottom + m_subtitleVerticalMargin < info.iSubtitles) + { + info.iSubtitles = info.Overscan.bottom + m_subtitleVerticalMargin; + + // We want the text to be at the base of the bar, + // then we shift the position to include the vertical margin + pControl->SetPosition((info.iWidth - pControl->GetWidth()) * 0.5f, + info.iSubtitles - pControl->GetHeight() + m_subtitlesHalfSpace - + m_subtitleVerticalMargin); + pControl->SetLocation(0, info.iSubtitles + m_subtitlesHalfSpace - m_subtitleVerticalMargin, + false); + } + + // Recalculate limits based on overscan values + const auto& size = m_controlsSize[CONTROL_SUBTITLES]; + const float scaledHeight = size.first / DEFAULT_GUI_HEIGHT * info.iHeight; + const float scaledSpace = + static_cast<float>(CONTROL_SUBTITLES_SPACE) / DEFAULT_GUI_HEIGHT * info.iHeight; + + m_subtitlesHalfSpace = static_cast<int>(scaledSpace / 2); + const int barHeight = static_cast<int>(scaledHeight - scaledSpace); + + pControl->SetLimits(0, m_subtitlesHalfSpace + barHeight + info.Overscan.top, 0, + info.Overscan.bottom + m_subtitlesHalfSpace); + } + } + + CServiceBroker::GetWinSystem()->GetGfxContext().SetResInfo(m_Res[m_iCurRes], info); + + return isOverscanChanged; +} + +void CGUIWindowSettingsScreenCalibration::FrameMove() +{ + m_iControl = GetFocusedControlID(); + if (m_iControl >= 0) + { + if (UpdateFromControl(m_iControl)) + { + // Send GUI_MSG_WINDOW_RESIZE to rescale font size/aspect for label controls + CServiceBroker::GetGUI()->GetWindowManager().SendMessage( + GUI_MSG_NOTIFY_ALL, WINDOW_SCREEN_CALIBRATION, 0, GUI_MSG_WINDOW_RESIZE); + } + } + else + { + SET_CONTROL_LABEL(CONTROL_LABEL_DESCRIPTION, ""); + SET_CONTROL_LABEL(CONTROL_LABEL_VALUE, ""); + SET_CONTROL_LABEL(CONTROL_LABEL_RES, ""); + } + CGUIWindow::FrameMove(); +} + +void CGUIWindowSettingsScreenCalibration::DoProcess(unsigned int currentTime, + CDirtyRegionList& dirtyregions) +{ + MarkDirtyRegion(); + + for (int i = CONTROL_TOP_LEFT; i <= CONTROL_RESET; i++) + SET_CONTROL_HIDDEN(i); + m_needsScaling = true; + CGUIWindow::DoProcess(currentTime, dirtyregions); + m_needsScaling = false; + + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(m_Res[m_iCurRes], false); + CServiceBroker::GetWinSystem()->GetGfxContext().AddGUITransform(); + + // process the movers etc. + for (int i = CONTROL_TOP_LEFT; i <= CONTROL_RESET; i++) + { + SET_CONTROL_VISIBLE(i); + CGUIControl* control = GetControl(i); + if (control) + control->DoProcess(currentTime, dirtyregions); + } + CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform(); +} + +void CGUIWindowSettingsScreenCalibration::DoRender() +{ + // we set that we need scaling here to render so that anything else on screen scales correctly + m_needsScaling = true; + CGUIWindow::DoRender(); + m_needsScaling = false; +} diff --git a/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h new file mode 100644 index 0000000..dd3e6b1 --- /dev/null +++ b/xbmc/settings/windows/GUIWindowSettingsScreenCalibration.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIWindow.h" + +#include <map> +#include <utility> +#include <vector> + +class CGUIWindowSettingsScreenCalibration : public CGUIWindow +{ +public: + CGUIWindowSettingsScreenCalibration(void); + ~CGUIWindowSettingsScreenCalibration(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + void DoProcess(unsigned int currentTime, CDirtyRegionList& dirtyregions) override; + void FrameMove() override; + void DoRender() override; + void AllocResources(bool forceLoad = false) override; + void FreeResources(bool forceUnLoad = false) override; + +protected: + unsigned int FindCurrentResolution(); + void NextControl(); + void ResetControls(); + void EnableControl(int iControl); + bool UpdateFromControl(int iControl); + void ResetCalibration(); + unsigned int m_iCurRes; + std::vector<RESOLUTION> m_Res; + int m_iControl; + float m_fPixelRatioBoxHeight; + +private: + std::map<int, std::pair<float, float>> m_controlsSize; + int m_subtitlesHalfSpace{0}; + int m_subtitleVerticalMargin{0}; + bool m_isSubtitleBarEnabled{false}; +}; |