diff options
Diffstat (limited to '')
-rw-r--r-- | xbmc/guilib/guiinfo/GUIInfoLabel.cpp | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/xbmc/guilib/guiinfo/GUIInfoLabel.cpp b/xbmc/guilib/guiinfo/GUIInfoLabel.cpp new file mode 100644 index 0000000..4a6ce13 --- /dev/null +++ b/xbmc/guilib/guiinfo/GUIInfoLabel.cpp @@ -0,0 +1,337 @@ +/* + * 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 "guilib/guiinfo/GUIInfoLabel.h" + +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "addons/Skin.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIListItem.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +using namespace KODI::GUILIB::GUIINFO; + +CGUIInfoLabel::CGUIInfoLabel(const std::string &label, const std::string &fallback /*= ""*/, int context /*= 0*/) +{ + SetLabel(label, fallback, context); +} + +int CGUIInfoLabel::GetIntValue(int contextWindow) const +{ + const std::string label = GetLabel(contextWindow); + if (!label.empty()) + return strtol(label.c_str(), NULL, 10); + + return 0; +} + +void CGUIInfoLabel::SetLabel(const std::string &label, const std::string &fallback, int context /*= 0*/) +{ + m_fallback = fallback; + Parse(label, context); +} + +const std::string &CGUIInfoLabel::GetLabel(int contextWindow, bool preferImage, std::string *fallback /*= NULL*/) const +{ + bool needsUpdate = m_dirty; + if (!m_info.empty()) + { + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + for (const auto &portion : m_info) + { + if (portion.m_info) + { + std::string infoLabel; + if (preferImage) + infoLabel = infoMgr.GetImage(portion.m_info, contextWindow, fallback); + if (infoLabel.empty()) + infoLabel = infoMgr.GetLabel(portion.m_info, contextWindow, fallback); + needsUpdate |= portion.NeedsUpdate(infoLabel); + } + } + } + else + needsUpdate = !m_label.empty(); + + return CacheLabel(needsUpdate); +} + +const std::string &CGUIInfoLabel::GetItemLabel(const CGUIListItem *item, bool preferImages, std::string *fallback /*= NULL*/) const +{ + bool needsUpdate = m_dirty; + if (item->IsFileItem() && !m_info.empty()) + { + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + for (const auto &portion : m_info) + { + if (portion.m_info) + { + std::string infoLabel; + if (preferImages) + infoLabel = infoMgr.GetItemImage(item, 0, portion.m_info, fallback); + else + infoLabel = infoMgr.GetItemLabel(static_cast<const CFileItem *>(item), 0, portion.m_info, fallback); + needsUpdate |= portion.NeedsUpdate(infoLabel); + } + } + } + else + needsUpdate = !m_label.empty(); + + return CacheLabel(needsUpdate); +} + +const std::string &CGUIInfoLabel::CacheLabel(bool rebuild) const +{ + if (rebuild) + { + m_label.clear(); + for (const auto &portion : m_info) + m_label += portion.Get(); + m_dirty = false; + } + if (m_label.empty()) // empty label, use the fallback + return m_fallback; + return m_label; +} + +bool CGUIInfoLabel::IsEmpty() const +{ + return m_info.empty(); +} + +bool CGUIInfoLabel::IsConstant() const +{ + return m_info.empty() || (m_info.size() == 1 && m_info[0].m_info == 0); +} + +bool CGUIInfoLabel::ReplaceSpecialKeywordReferences(const std::string &strInput, const std::string &strKeyword, const StringReplacerFunc &func, std::string &strOutput) +{ + // replace all $strKeyword[value] with resolved strings + const std::string dollarStrPrefix = "$" + strKeyword + "["; + + size_t index = 0; + size_t startPos; + + while ((startPos = strInput.find(dollarStrPrefix, index)) != std::string::npos) + { + size_t valuePos = startPos + dollarStrPrefix.size(); + size_t endPos = StringUtils::FindEndBracket(strInput, '[', ']', valuePos); + if (endPos != std::string::npos) + { + if (index == 0) // first occurrence? + strOutput.clear(); + strOutput.append(strInput, index, startPos - index); // append part from the left side + strOutput += func(strInput.substr(valuePos, endPos - valuePos)); // resolve and append value part + index = endPos + 1; + } + else + { + // if closing bracket is missing, report error and leave incomplete reference in + CLog::Log(LOGERROR, "Error parsing value - missing ']' in \"{}\"", strInput); + break; + } + } + + if (index) // if we replaced anything + { + strOutput.append(strInput, index, std::string::npos); // append leftover from the right side + return true; + } + + return false; +} + +bool CGUIInfoLabel::ReplaceSpecialKeywordReferences(std::string &work, const std::string &strKeyword, const StringReplacerFunc &func) +{ + std::string output; + if (ReplaceSpecialKeywordReferences(work, strKeyword, func, output)) + { + work = std::move(output); + return true; + } + return false; +} + +std::string LocalizeReplacer(const std::string &str) +{ + std::string replace = g_localizeStringsTemp.Get(atoi(str.c_str())); + if (replace.empty()) + replace = g_localizeStrings.Get(atoi(str.c_str())); + return replace; +} + +std::string AddonReplacer(const std::string &str) +{ + // assumes "addon.id #####" + size_t length = str.find(' '); + const std::string addonid = str.substr(0, length); + int stringid = atoi(str.substr(length + 1).c_str()); + return g_localizeStrings.GetAddonString(addonid, stringid); +} + +std::string NumberReplacer(const std::string &str) +{ + return str; +} + +std::string CGUIInfoLabel::ReplaceLocalize(const std::string &label) +{ + std::string work(label); + ReplaceSpecialKeywordReferences(work, "LOCALIZE", LocalizeReplacer); + ReplaceSpecialKeywordReferences(work, "NUMBER", NumberReplacer); + return work; +} + +std::string CGUIInfoLabel::ReplaceAddonStrings(std::string &&label) +{ + ReplaceSpecialKeywordReferences(label, "ADDON", AddonReplacer); + return std::move(label); +} + +enum EINFOFORMAT { NONE = 0, FORMATINFO, FORMATESCINFO, FORMATVAR, FORMATESCVAR }; + +typedef struct +{ + const char *str; + EINFOFORMAT val; +} infoformat; + +const static infoformat infoformatmap[] = {{ "$INFO[", FORMATINFO}, + { "$ESCINFO[", FORMATESCINFO}, + { "$VAR[", FORMATVAR}, + { "$ESCVAR[", FORMATESCVAR}}; + +void CGUIInfoLabel::Parse(const std::string &label, int context) +{ + m_info.clear(); + m_dirty = true; + // Step 1: Replace all $LOCALIZE[number] with the real string + std::string work = ReplaceLocalize(label); + // Step 2: Replace all $ADDON[id number] with the real string + work = ReplaceAddonStrings(std::move(work)); + // Step 3: Find all $INFO[info,prefix,postfix] blocks + EINFOFORMAT format; + do + { + format = NONE; + size_t pos1 = work.size(); + size_t pos2; + size_t len = 0; + for (const infoformat& infoformat : infoformatmap) + { + pos2 = work.find(infoformat.str); + if (pos2 != std::string::npos && pos2 < pos1) + { + pos1 = pos2; + len = strlen(infoformat.str); + format = infoformat.val; + } + } + + if (format != NONE) + { + if (pos1 > 0) + m_info.emplace_back(0, work.substr(0, pos1), ""); + + pos2 = StringUtils::FindEndBracket(work, '[', ']', pos1 + len); + if (pos2 != std::string::npos) + { + // decipher the block + std::vector<std::string> params = StringUtils::Split(work.substr(pos1 + len, pos2 - pos1 - len), ","); + if (!params.empty()) + { + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + + int info; + if (format == FORMATVAR || format == FORMATESCVAR) + { + info = infoMgr.TranslateSkinVariableString(params[0], context); + if (info == 0) + info = infoMgr.RegisterSkinVariableString(g_SkinInfo->CreateSkinVariable(params[0], context)); + if (info == 0) // skinner didn't define this conditional label! + CLog::Log(LOGWARNING, "Label Formatting: $VAR[{}] is not defined", params[0]); + } + else + info = infoMgr.TranslateString(params[0]); + std::string prefix, postfix; + if (params.size() > 1) + prefix = params[1]; + if (params.size() > 2) + postfix = params[2]; + m_info.emplace_back(info, prefix, postfix, format == FORMATESCINFO || format == FORMATESCVAR); + } + // and delete it from our work string + work.erase(0, pos2 + 1); + } + else + { + CLog::Log(LOGERROR, "Error parsing label - missing ']' in \"{}\"", label); + return; + } + } + } + while (format != NONE); + + if (!work.empty()) + m_info.emplace_back(0, work, ""); +} + +CGUIInfoLabel::CInfoPortion::CInfoPortion(int info, const std::string &prefix, const std::string &postfix, bool escaped /*= false */): + m_prefix(prefix), + m_postfix(postfix) +{ + m_info = info; + m_escaped = escaped; + // filter our prefix and postfix for comma's + StringUtils::Replace(m_prefix, "$COMMA", ","); + StringUtils::Replace(m_postfix, "$COMMA", ","); + StringUtils::Replace(m_prefix, "$LBRACKET", "["); StringUtils::Replace(m_prefix, "$RBRACKET", "]"); + StringUtils::Replace(m_postfix, "$LBRACKET", "["); StringUtils::Replace(m_postfix, "$RBRACKET", "]"); +} + +bool CGUIInfoLabel::CInfoPortion::NeedsUpdate(const std::string &label) const +{ + if (m_label != label) + { + m_label = label; + return true; + } + return false; +} + +std::string CGUIInfoLabel::CInfoPortion::Get() const +{ + if (!m_info) + return m_prefix; + else if (m_label.empty()) + return ""; + std::string label = m_prefix + m_label + m_postfix; + if (m_escaped) // escape all quotes and backslashes, then quote + { + StringUtils::Replace(label, "\\", "\\\\"); + StringUtils::Replace(label, "\"", "\\\""); + return "\"" + label + "\""; + } + return label; +} + +std::string CGUIInfoLabel::GetLabel(const std::string &label, int contextWindow /*= 0*/, bool preferImage /*= false */) +{ // translate the label + const CGUIInfoLabel info(label, "", contextWindow); + return info.GetLabel(contextWindow, preferImage); +} + +std::string CGUIInfoLabel::GetItemLabel(const std::string &label, const CGUIListItem *item, bool preferImage /*= false */) +{ // translate the label + const CGUIInfoLabel info(label); + return info.GetItemLabel(item, preferImage); +} |