/* * 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 "GUIControlFactory.h" #include "GUIAction.h" #include "GUIBorderedImage.h" #include "GUIButtonControl.h" #include "GUIColorButtonControl.h" #include "GUIColorManager.h" #include "GUIControlGroup.h" #include "GUIControlGroupList.h" #include "GUIEditControl.h" #include "GUIFadeLabelControl.h" #include "GUIFixedListContainer.h" #include "GUIFontManager.h" #include "GUIImage.h" #include "GUIInfoManager.h" #include "GUILabelControl.h" #include "GUIListContainer.h" #include "GUIListGroup.h" #include "GUIListLabel.h" #include "GUIMoverControl.h" #include "GUIMultiImage.h" #include "GUIPanelContainer.h" #include "GUIProgressControl.h" #include "GUIRSSControl.h" #include "GUIRadioButtonControl.h" #include "GUIRangesControl.h" #include "GUIRenderingControl.h" #include "GUIResizeControl.h" #include "GUIScrollBarControl.h" #include "GUISettingsSliderControl.h" #include "GUISliderControl.h" #include "GUISpinControl.h" #include "GUISpinControlEx.h" #include "GUITextBox.h" #include "GUIToggleButtonControl.h" #include "GUIVideoControl.h" #include "GUIVisualisationControl.h" #include "GUIWrappingListContainer.h" #include "LocalizeStrings.h" #include "addons/Skin.h" #include "cores/RetroPlayer/guicontrols/GUIGameControl.h" #include "games/controllers/guicontrols/GUIGameController.h" #include "input/Key.h" #include "pvr/guilib/GUIEPGGridContainer.h" #include "utils/CharsetConverter.h" #include "utils/RssManager.h" #include "utils/StringUtils.h" #include "utils/XMLUtils.h" #include "utils/log.h" using namespace KODI; using namespace KODI::GUILIB; using namespace PVR; typedef struct { const char* name; CGUIControl::GUICONTROLTYPES type; } ControlMapping; static const ControlMapping controls[] = { {"button", CGUIControl::GUICONTROL_BUTTON}, {"colorbutton", CGUIControl::GUICONTROL_COLORBUTTON}, {"edit", CGUIControl::GUICONTROL_EDIT}, {"epggrid", CGUIControl::GUICONTAINER_EPGGRID}, {"fadelabel", CGUIControl::GUICONTROL_FADELABEL}, {"fixedlist", CGUIControl::GUICONTAINER_FIXEDLIST}, {"gamecontroller", CGUIControl::GUICONTROL_GAMECONTROLLER}, {"gamewindow", CGUIControl::GUICONTROL_GAME}, {"group", CGUIControl::GUICONTROL_GROUP}, {"group", CGUIControl::GUICONTROL_LISTGROUP}, {"grouplist", CGUIControl::GUICONTROL_GROUPLIST}, {"image", CGUIControl::GUICONTROL_IMAGE}, {"image", CGUIControl::GUICONTROL_BORDEREDIMAGE}, {"label", CGUIControl::GUICONTROL_LABEL}, {"label", CGUIControl::GUICONTROL_LISTLABEL}, {"list", CGUIControl::GUICONTAINER_LIST}, {"mover", CGUIControl::GUICONTROL_MOVER}, {"multiimage", CGUIControl::GUICONTROL_MULTI_IMAGE}, {"panel", CGUIControl::GUICONTAINER_PANEL}, {"progress", CGUIControl::GUICONTROL_PROGRESS}, {"radiobutton", CGUIControl::GUICONTROL_RADIO}, {"ranges", CGUIControl::GUICONTROL_RANGES}, {"renderaddon", CGUIControl::GUICONTROL_RENDERADDON}, {"resize", CGUIControl::GUICONTROL_RESIZE}, {"rss", CGUIControl::GUICONTROL_RSS}, {"scrollbar", CGUIControl::GUICONTROL_SCROLLBAR}, {"slider", CGUIControl::GUICONTROL_SLIDER}, {"sliderex", CGUIControl::GUICONTROL_SETTINGS_SLIDER}, {"spincontrol", CGUIControl::GUICONTROL_SPIN}, {"spincontrolex", CGUIControl::GUICONTROL_SPINEX}, {"textbox", CGUIControl::GUICONTROL_TEXTBOX}, {"togglebutton", CGUIControl::GUICONTROL_TOGGLEBUTTON}, {"videowindow", CGUIControl::GUICONTROL_VIDEO}, {"visualisation", CGUIControl::GUICONTROL_VISUALISATION}, {"wraplist", CGUIControl::GUICONTAINER_WRAPLIST}, }; CGUIControl::GUICONTROLTYPES CGUIControlFactory::TranslateControlType(const std::string &type) { for (const ControlMapping& control : controls) if (StringUtils::EqualsNoCase(type, control.name)) return control.type; return CGUIControl::GUICONTROL_UNKNOWN; } std::string CGUIControlFactory::TranslateControlType(CGUIControl::GUICONTROLTYPES type) { for (const ControlMapping& control : controls) if (type == control.type) return control.name; return ""; } CGUIControlFactory::CGUIControlFactory(void) = default; CGUIControlFactory::~CGUIControlFactory(void) = default; bool CGUIControlFactory::GetIntRange(const TiXmlNode* pRootNode, const char* strTag, int& iMinValue, int& iMaxValue, int& iIntervalValue) { const TiXmlNode* pNode = pRootNode->FirstChild(strTag); if (!pNode || !pNode->FirstChild()) return false; iMinValue = atoi(pNode->FirstChild()->Value()); const char* maxValue = strchr(pNode->FirstChild()->Value(), ','); if (maxValue) { maxValue++; iMaxValue = atoi(maxValue); const char* intervalValue = strchr(maxValue, ','); if (intervalValue) { intervalValue++; iIntervalValue = atoi(intervalValue); } } return true; } bool CGUIControlFactory::GetFloatRange(const TiXmlNode* pRootNode, const char* strTag, float& fMinValue, float& fMaxValue, float& fIntervalValue) { const TiXmlNode* pNode = pRootNode->FirstChild(strTag); if (!pNode || !pNode->FirstChild()) return false; fMinValue = (float)atof(pNode->FirstChild()->Value()); const char* maxValue = strchr(pNode->FirstChild()->Value(), ','); if (maxValue) { maxValue++; fMaxValue = (float)atof(maxValue); const char* intervalValue = strchr(maxValue, ','); if (intervalValue) { intervalValue++; fIntervalValue = (float)atof(intervalValue); } } return true; } float CGUIControlFactory::ParsePosition(const char* pos, const float parentSize) { char* end = NULL; float value = pos ? (float)strtod(pos, &end) : 0; if (end) { if (*end == 'r') value = parentSize - value; else if (*end == '%') value = value * parentSize / 100.0f; } return value; } bool CGUIControlFactory::GetPosition(const TiXmlNode *node, const char* strTag, const float parentSize, float& value) { const TiXmlElement* pNode = node->FirstChildElement(strTag); if (!pNode || !pNode->FirstChild()) return false; value = ParsePosition(pNode->FirstChild()->Value(), parentSize); return true; } bool CGUIControlFactory::GetDimension(const TiXmlNode *pRootNode, const char* strTag, const float parentSize, float &value, float &min) { const TiXmlElement* pNode = pRootNode->FirstChildElement(strTag); if (!pNode || !pNode->FirstChild()) return false; if (0 == StringUtils::CompareNoCase("auto", pNode->FirstChild()->Value(), 4)) { // auto-width - at least min must be set value = ParsePosition(pNode->Attribute("max"), parentSize); min = ParsePosition(pNode->Attribute("min"), parentSize); if (!min) min = 1; return true; } value = ParsePosition(pNode->FirstChild()->Value(), parentSize); return true; } bool CGUIControlFactory::GetDimensions(const TiXmlNode *node, const char *leftTag, const char *rightTag, const char *centerLeftTag, const char *centerRightTag, const char *widthTag, const float parentSize, float &left, float &width, float &min_width) { float center = 0, right = 0; // read from the XML bool hasLeft = GetPosition(node, leftTag, parentSize, left); bool hasCenter = GetPosition(node, centerLeftTag, parentSize, center); if (!hasCenter && GetPosition(node, centerRightTag, parentSize, center)) { center = parentSize - center; hasCenter = true; } bool hasRight = false; if (GetPosition(node, rightTag, parentSize, right)) { right = parentSize - right; hasRight = true; } bool hasWidth = GetDimension(node, widthTag, parentSize, width, min_width); if (!hasLeft) { // figure out position if (hasCenter) // no left specified { if (hasWidth) { left = center - width/2; hasLeft = true; } else { if (hasRight) { width = (right - center) * 2; left = right - width; hasLeft = true; } } } else if (hasRight) // no left or centre { if (hasWidth) { left = right - width; hasLeft = true; } } } if (!hasWidth) { if (hasRight) { width = std::max(0.0f, right - left); // if left=0, this fills to size of parent hasLeft = true; hasWidth = true; } else if (hasCenter) { if (hasLeft) { width = std::max(0.0f, (center - left) * 2); hasWidth = true; } else if (center > 0 && center < parentSize) { // centre given, so fill to edge of parent width = std::max(0.0f, std::min(parentSize - center, center) * 2); left = center - width/2; hasLeft = true; hasWidth = true; } } else if (hasLeft) // neither right nor center specified { width = std::max(0.0f, parentSize - left); // if left=0, this fills to parent hasWidth = true; } } return hasLeft && hasWidth; } bool CGUIControlFactory::GetAspectRatio(const TiXmlNode* pRootNode, const char* strTag, CAspectRatio &aspect) { std::string ratio; const TiXmlElement *node = pRootNode->FirstChildElement(strTag); if (!node || !node->FirstChild()) return false; ratio = node->FirstChild()->Value(); if (StringUtils::EqualsNoCase(ratio, "keep")) aspect.ratio = CAspectRatio::AR_KEEP; else if (StringUtils::EqualsNoCase(ratio, "scale")) aspect.ratio = CAspectRatio::AR_SCALE; else if (StringUtils::EqualsNoCase(ratio, "center")) aspect.ratio = CAspectRatio::AR_CENTER; else if (StringUtils::EqualsNoCase(ratio, "stretch")) aspect.ratio = CAspectRatio::AR_STRETCH; const char *attribute = node->Attribute("align"); if (attribute) { std::string align(attribute); if (StringUtils::EqualsNoCase(align, "center")) aspect.align = ASPECT_ALIGN_CENTER | (aspect.align & ASPECT_ALIGNY_MASK); else if (StringUtils::EqualsNoCase(align, "right")) aspect.align = ASPECT_ALIGN_RIGHT | (aspect.align & ASPECT_ALIGNY_MASK); else if (StringUtils::EqualsNoCase(align, "left")) aspect.align = ASPECT_ALIGN_LEFT | (aspect.align & ASPECT_ALIGNY_MASK); } attribute = node->Attribute("aligny"); if (attribute) { std::string align(attribute); if (StringUtils::EqualsNoCase(align, "center")) aspect.align = ASPECT_ALIGNY_CENTER | (aspect.align & ASPECT_ALIGN_MASK); else if (StringUtils::EqualsNoCase(align, "bottom")) aspect.align = ASPECT_ALIGNY_BOTTOM | (aspect.align & ASPECT_ALIGN_MASK); else if (StringUtils::EqualsNoCase(align, "top")) aspect.align = ASPECT_ALIGNY_TOP | (aspect.align & ASPECT_ALIGN_MASK); } attribute = node->Attribute("scalediffuse"); if (attribute) { std::string scale(attribute); if (StringUtils::EqualsNoCase(scale, "true") || StringUtils::EqualsNoCase(scale, "yes")) aspect.scaleDiffuse = true; else aspect.scaleDiffuse = false; } return true; } bool CGUIControlFactory::GetInfoTexture(const TiXmlNode* pRootNode, const char* strTag, CTextureInfo &image, GUIINFO::CGUIInfoLabel &info, int parentID) { GetTexture(pRootNode, strTag, image); image.filename = ""; GetInfoLabel(pRootNode, strTag, info, parentID); return true; } bool CGUIControlFactory::GetTexture(const TiXmlNode* pRootNode, const char* strTag, CTextureInfo &image) { const TiXmlElement* pNode = pRootNode->FirstChildElement(strTag); if (!pNode) return false; const char *border = pNode->Attribute("border"); if (border) { GetRectFromString(border, image.border); const char* borderinfill = pNode->Attribute("infill"); image.m_infill = (!borderinfill || !StringUtils::EqualsNoCase(borderinfill, "false")); } image.orientation = 0; const char *flipX = pNode->Attribute("flipx"); if (flipX && StringUtils::CompareNoCase(flipX, "true") == 0) image.orientation = 1; const char *flipY = pNode->Attribute("flipy"); if (flipY && StringUtils::CompareNoCase(flipY, "true") == 0) image.orientation = 3 - image.orientation; // either 3 or 2 image.diffuse = XMLUtils::GetAttribute(pNode, "diffuse"); image.diffuseColor.Parse(XMLUtils::GetAttribute(pNode, "colordiffuse"), 0); const char *background = pNode->Attribute("background"); if (background && StringUtils::CompareNoCase(background, "true", 4) == 0) image.useLarge = true; image.filename = pNode->FirstChild() ? pNode->FirstChild()->Value() : ""; return true; } void CGUIControlFactory::GetRectFromString(const std::string &string, CRect &rect) { // format is rect="left[,top,right,bottom]" std::vector strRect = StringUtils::Split(string, ','); if (strRect.size() == 1) { rect.x1 = (float)atof(strRect[0].c_str()); rect.y1 = rect.x1; rect.x2 = rect.x1; rect.y2 = rect.x1; } else if (strRect.size() == 4) { rect.x1 = (float)atof(strRect[0].c_str()); rect.y1 = (float)atof(strRect[1].c_str()); rect.x2 = (float)atof(strRect[2].c_str()); rect.y2 = (float)atof(strRect[3].c_str()); } } bool CGUIControlFactory::GetAlignment(const TiXmlNode* pRootNode, const char* strTag, uint32_t& alignment) { const TiXmlNode* pNode = pRootNode->FirstChild(strTag); if (!pNode || !pNode->FirstChild()) return false; std::string strAlign = pNode->FirstChild()->Value(); if (strAlign == "right" || strAlign == "bottom") alignment = XBFONT_RIGHT; else if (strAlign == "center") alignment = XBFONT_CENTER_X; else if (strAlign == "justify") alignment = XBFONT_JUSTIFIED; else alignment = XBFONT_LEFT; return true; } bool CGUIControlFactory::GetAlignmentY(const TiXmlNode* pRootNode, const char* strTag, uint32_t& alignment) { const TiXmlNode* pNode = pRootNode->FirstChild(strTag ); if (!pNode || !pNode->FirstChild()) { return false; } std::string strAlign = pNode->FirstChild()->Value(); alignment = 0; if (strAlign == "center") { alignment = XBFONT_CENTER_Y; } return true; } bool CGUIControlFactory::GetConditionalVisibility(const TiXmlNode* control, std::string &condition, std::string &allowHiddenFocus) { const TiXmlElement* node = control->FirstChildElement("visible"); if (!node) return false; std::vector conditions; while (node) { const char *hidden = node->Attribute("allowhiddenfocus"); if (hidden) allowHiddenFocus = hidden; // add to our condition string if (!node->NoChildren()) conditions.emplace_back(node->FirstChild()->Value()); node = node->NextSiblingElement("visible"); } if (!conditions.size()) return false; if (conditions.size() == 1) condition = conditions[0]; else { // multiple conditions should be anded together condition = "["; for (unsigned int i = 0; i < conditions.size() - 1; i++) condition += conditions[i] + "] + ["; condition += conditions[conditions.size() - 1] + "]"; } return true; } bool CGUIControlFactory::GetConditionalVisibility(const TiXmlNode *control, std::string &condition) { std::string allowHiddenFocus; return GetConditionalVisibility(control, condition, allowHiddenFocus); } bool CGUIControlFactory::GetAnimations(TiXmlNode *control, const CRect &rect, int context, std::vector &animations) { TiXmlElement* node = control->FirstChildElement("animation"); bool ret = false; if (node) animations.clear(); while (node) { ret = true; if (node->FirstChild()) { CAnimation anim; anim.Create(node, rect, context); animations.push_back(anim); if (StringUtils::CompareNoCase(node->FirstChild()->Value(), "VisibleChange") == 0) { // add the hidden one as well TiXmlElement hidden(*node); hidden.FirstChild()->SetValue("hidden"); const char *start = hidden.Attribute("start"); const char *end = hidden.Attribute("end"); if (start && end) { std::string temp = end; hidden.SetAttribute("end", start); hidden.SetAttribute("start", temp.c_str()); } else if (start) hidden.SetAttribute("end", start); else if (end) hidden.SetAttribute("start", end); CAnimation anim2; anim2.Create(&hidden, rect, context); animations.push_back(anim2); } } node = node->NextSiblingElement("animation"); } return ret; } bool CGUIControlFactory::GetActions(const TiXmlNode* pRootNode, const char* strTag, CGUIAction& actions) { actions.Reset(); const TiXmlElement* pElement = pRootNode->FirstChildElement(strTag); while (pElement) { if (pElement->FirstChild()) { actions.Append( {XMLUtils::GetAttribute(pElement, "condition"), pElement->FirstChild()->Value()}); } pElement = pElement->NextSiblingElement(strTag); } return actions.HasAnyActions(); } bool CGUIControlFactory::GetHitRect(const TiXmlNode *control, CRect &rect, const CRect &parentRect) { const TiXmlElement* node = control->FirstChildElement("hitrect"); if (node) { rect.x1 = ParsePosition(node->Attribute("x"), parentRect.Width()); rect.y1 = ParsePosition(node->Attribute("y"), parentRect.Height()); if (node->Attribute("w")) rect.x2 = (float)atof(node->Attribute("w")) + rect.x1; else if (node->Attribute("right")) rect.x2 = std::min(ParsePosition(node->Attribute("right"), parentRect.Width()), rect.x1); if (node->Attribute("h")) rect.y2 = (float)atof(node->Attribute("h")) + rect.y1; else if (node->Attribute("bottom")) rect.y2 = std::min(ParsePosition(node->Attribute("bottom"), parentRect.Height()), rect.y1); return true; } return false; } bool CGUIControlFactory::GetScroller(const TiXmlNode *control, const std::string &scrollerTag, CScroller& scroller) { const TiXmlElement* node = control->FirstChildElement(scrollerTag); if (node) { unsigned int scrollTime; if (XMLUtils::GetUInt(control, scrollerTag.c_str(), scrollTime)) { scroller = CScroller(scrollTime, CAnimEffect::GetTweener(node)); return true; } } return false; } bool CGUIControlFactory::GetColor(const TiXmlNode* control, const char* strTag, UTILS::COLOR::Color& value) { const TiXmlElement* node = control->FirstChildElement(strTag); if (node && node->FirstChild()) { value = CServiceBroker::GetGUI()->GetColorManager().GetColor(node->FirstChild()->Value()); return true; } return false; } bool CGUIControlFactory::GetInfoColor(const TiXmlNode *control, const char *strTag, GUIINFO::CGUIInfoColor &value,int parentID) { const TiXmlElement* node = control->FirstChildElement(strTag); if (node && node->FirstChild()) { value.Parse(node->FirstChild()->ValueStr(), parentID); return true; } return false; } void CGUIControlFactory::GetInfoLabel(const TiXmlNode *pControlNode, const std::string &labelTag, GUIINFO::CGUIInfoLabel &infoLabel, int parentID) { std::vector labels; GetInfoLabels(pControlNode, labelTag, labels, parentID); if (labels.size()) infoLabel = labels[0]; } bool CGUIControlFactory::GetInfoLabelFromElement(const TiXmlElement *element, GUIINFO::CGUIInfoLabel &infoLabel, int parentID) { if (!element || !element->FirstChild()) return false; std::string label = element->FirstChild()->Value(); if (label.empty()) return false; std::string fallback = XMLUtils::GetAttribute(element, "fallback"); if (StringUtils::IsNaturalNumber(label)) label = g_localizeStrings.Get(atoi(label.c_str())); if (StringUtils::IsNaturalNumber(fallback)) fallback = g_localizeStrings.Get(atoi(fallback.c_str())); else g_charsetConverter.unknownToUTF8(fallback); infoLabel.SetLabel(label, fallback, parentID); return true; } void CGUIControlFactory::GetInfoLabels(const TiXmlNode *pControlNode, const std::string &labelTag, std::vector &infoLabels, int parentID) { // we can have the following infolabels: // 1. 1234 -> direct number // 2. -> lookup in localizestrings // 3. -> infolabel with given fallback // 4. ListItem.Album (uses