changed: Add logic to properly handle subtitles for stacked files
[vuplus_xbmc] / xbmc / guilib / GUIInfoTypes.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIInfoTypes.h"
22 #include "GUIInfoManager.h"
23 #include "addons/AddonManager.h"
24 #include "utils/log.h"
25 #include "LocalizeStrings.h"
26 #include "GUIColorManager.h"
27 #include "GUIListItem.h"
28 #include "utils/StringUtils.h"
29 #include "addons/Skin.h"
30
31 using namespace std;
32 using ADDON::CAddonMgr;
33
34 CGUIInfoBool::CGUIInfoBool(bool value)
35 {
36   m_value = value;
37 }
38
39 CGUIInfoBool::~CGUIInfoBool()
40 {
41 }
42
43 void CGUIInfoBool::Parse(const CStdString &expression, int context)
44 {
45   if (expression == "true")
46     m_value = true;
47   else if (expression == "false")
48     m_value = false;
49   else
50   {
51     m_info = g_infoManager.Register(expression, context);
52     Update();
53   }
54 }
55
56 void CGUIInfoBool::Update(const CGUIListItem *item /*= NULL*/)
57 {
58   if (m_info)
59     m_value = m_info->Get(item);
60 }
61
62
63 CGUIInfoColor::CGUIInfoColor(uint32_t color)
64 {
65   m_color = color;
66   m_info = 0;
67 }
68
69 CGUIInfoColor &CGUIInfoColor::operator=(color_t color)
70 {
71   m_color = color;
72   m_info = 0;
73   return *this;
74 }
75
76 CGUIInfoColor &CGUIInfoColor::operator=(const CGUIInfoColor &color)
77 {
78   m_color = color.m_color;
79   m_info = color.m_info;
80   return *this;
81 }
82
83 bool CGUIInfoColor::Update()
84 {
85   if (!m_info)
86     return false; // no infolabel
87
88   // Expand the infolabel, and then convert it to a color
89   CStdString infoLabel(g_infoManager.GetLabel(m_info));
90   color_t color = !infoLabel.empty() ? g_colorManager.GetColor(infoLabel.c_str()) : 0;
91   if (m_color != color)
92   {
93     m_color = color;
94     return true;
95   }
96   else
97     return false;
98 }
99
100 void CGUIInfoColor::Parse(const CStdString &label, int context)
101 {
102   // Check for the standard $INFO[] block layout, and strip it if present
103   CStdString label2 = label;
104   if (label.Equals("-", false))
105     return;
106
107   if (StringUtils::StartsWithNoCase(label, "$var["))
108   {
109     label2 = label.substr(5, label.length() - 6);
110     m_info = g_infoManager.TranslateSkinVariableString(label2, context);
111     if (!m_info)
112       m_info = g_infoManager.RegisterSkinVariableString(g_SkinInfo->CreateSkinVariable(label2, context));
113     return;
114   }
115
116   if (StringUtils::StartsWithNoCase(label, "$info["))
117     label2 = label.substr(6, label.length()-7);
118
119   m_info = g_infoManager.TranslateString(label2);
120   if (!m_info)
121     m_color = g_colorManager.GetColor(label);
122 }
123
124 CGUIInfoLabel::CGUIInfoLabel()
125 {
126 }
127
128 CGUIInfoLabel::CGUIInfoLabel(const CStdString &label, const CStdString &fallback /*= ""*/, int context /*= 0*/)
129 {
130   SetLabel(label, fallback, context);
131 }
132
133 void CGUIInfoLabel::SetLabel(const CStdString &label, const CStdString &fallback, int context /*= 0*/)
134 {
135   m_fallback = fallback;
136   Parse(label, context);
137 }
138
139 CStdString CGUIInfoLabel::GetLabel(int contextWindow, bool preferImage, CStdString *fallback /*= NULL*/) const
140 {
141   CStdString label;
142   for (unsigned int i = 0; i < m_info.size(); i++)
143   {
144     const CInfoPortion &portion = m_info[i];
145     if (portion.m_info)
146     {
147       CStdString infoLabel;
148       if (preferImage)
149         infoLabel = g_infoManager.GetImage(portion.m_info, contextWindow, fallback);
150       if (infoLabel.empty())
151         infoLabel = g_infoManager.GetLabel(portion.m_info, contextWindow, fallback);
152       if (!infoLabel.empty())
153         label += portion.GetLabel(infoLabel);
154     }
155     else
156     { // no info, so just append the prefix
157       label += portion.m_prefix;
158     }
159   }
160   if (label.empty())  // empty label, use the fallback
161     return m_fallback;
162   return label;
163 }
164
165 CStdString CGUIInfoLabel::GetItemLabel(const CGUIListItem *item, bool preferImages, CStdString *fallback /*= NULL*/) const
166 {
167   if (!item->IsFileItem()) return "";
168   CStdString label;
169   for (unsigned int i = 0; i < m_info.size(); i++)
170   {
171     const CInfoPortion &portion = m_info[i];
172     if (portion.m_info)
173     {
174       CStdString infoLabel;
175       if (preferImages)
176         infoLabel = g_infoManager.GetItemImage((const CFileItem *)item, portion.m_info, fallback);
177       else
178         infoLabel = g_infoManager.GetItemLabel((const CFileItem *)item, portion.m_info, fallback);
179       if (!infoLabel.empty())
180         label += portion.GetLabel(infoLabel);
181     }
182     else
183     { // no info, so just append the prefix
184       label += portion.m_prefix;
185     }
186   }
187   if (label.empty())
188     return m_fallback;
189   return label;
190 }
191
192 bool CGUIInfoLabel::IsEmpty() const
193 {
194   return m_info.size() == 0;
195 }
196
197 bool CGUIInfoLabel::IsConstant() const
198 {
199   return m_info.size() == 0 || (m_info.size() == 1 && m_info[0].m_info == 0);
200 }
201
202 CStdString CGUIInfoLabel::ReplaceLocalize(const CStdString &label)
203 {
204   CStdString work(label);
205   // Replace all $LOCALIZE[number] with the real string
206   size_t pos1 = work.find("$LOCALIZE[");
207   while (pos1 != std::string::npos)
208   {
209     size_t pos2 = StringUtils::FindEndBracket(work, '[', ']', pos1 + 10);
210     if (pos2 != std::string::npos)
211     {
212       CStdString left = work.substr(0, pos1);
213       CStdString right = work.substr(pos2 + 1);
214       CStdString replace = g_localizeStringsTemp.Get(atoi(work.substr(pos1 + 10).c_str()));
215       if (replace == "")
216          replace = g_localizeStrings.Get(atoi(work.substr(pos1 + 10).c_str()));
217       work = left + replace + right;
218     }
219     else
220     {
221       CLog::Log(LOGERROR, "Error parsing label - missing ']' in \"%s\"", label.c_str());
222       return "";
223     }
224     pos1 = work.find("$LOCALIZE[", pos1);
225   }
226   return work;
227 }
228
229 CStdString CGUIInfoLabel::ReplaceAddonStrings(const CStdString &label)
230 {
231   CStdString work(label);
232   //FIXME why not use RE here?
233   // Replace all $ADDON[id number] with the real string
234   size_t pos1 = work.find("$ADDON[");
235   while (pos1 != std::string::npos)
236   {
237     size_t pos2 = StringUtils::FindEndBracket(work, '[', ']', pos1 + 7);
238     if (pos2 != std::string::npos)
239     {
240       CStdString left = work.substr(0, pos1);
241       CStdString right = work.substr(pos2 + 1);
242       size_t length = work.find(" ", pos1 + 7) - (pos1 + 7);
243       CStdString id = work.substr(pos1 + 7, length);
244       int stringid = atoi(work.substr(pos1 + 7 + id.length() + 1, 5).c_str());
245       CStdString replace = CAddonMgr::Get().GetString(id, stringid);
246       work = left + replace + right;
247     }
248     else
249     {
250       CLog::Log(LOGERROR, "Error parsing label - missing ']' in \"%s\"", label.c_str());
251       return "";
252     }
253     pos1 = work.find("$ADDON[", pos1);
254   }
255   return work;
256 }
257
258 enum EINFOFORMAT { NONE = 0, FORMATINFO, FORMATESCINFO, FORMATVAR };
259
260 typedef struct
261 {
262   const char *str;
263   EINFOFORMAT  val;
264 } infoformat;
265
266 const static infoformat infoformatmap[] = {{ "$INFO[",    FORMATINFO },
267                                            { "$ESCINFO[", FORMATESCINFO},
268                                            { "$VAR[",     FORMATVAR}};
269
270 void CGUIInfoLabel::Parse(const CStdString &label, int context)
271 {
272   m_info.clear();
273   // Step 1: Replace all $LOCALIZE[number] with the real string
274   CStdString work = ReplaceLocalize(label);
275   // Step 2: Replace all $ADDON[id number] with the real string
276   work = ReplaceAddonStrings(work);
277   // Step 3: Find all $INFO[info,prefix,postfix] blocks
278   EINFOFORMAT format;
279   do
280   {
281     format = NONE;
282     size_t pos1 = work.size();
283     size_t pos2;
284     size_t len = 0;
285     for (size_t i = 0; i < sizeof(infoformatmap) / sizeof(infoformat); i++)
286     {
287       pos2 = work.find(infoformatmap[i].str);
288       if (pos2 != string::npos && pos2 < pos1)
289       {
290         pos1 = pos2;
291         len = strlen(infoformatmap[i].str);
292         format = infoformatmap[i].val;
293       }
294     }
295
296     if (format != NONE)
297     {
298       if (pos1 > 0)
299         m_info.push_back(CInfoPortion(0, work.substr(0, pos1), ""));
300
301       pos2 = StringUtils::FindEndBracket(work, '[', ']', pos1 + len);
302       if (pos2 != std::string::npos)
303       {
304         // decipher the block
305         CStdString block = work.substr(pos1 + len, pos2 - pos1 - len);
306         CStdStringArray params;
307         StringUtils::SplitString(block, ",", params);
308         int info;
309         if (format == FORMATVAR)
310         {
311           info = g_infoManager.TranslateSkinVariableString(params[0], context);
312           if (info == 0)
313             info = g_infoManager.RegisterSkinVariableString(g_SkinInfo->CreateSkinVariable(params[0], context));
314           if (info == 0) // skinner didn't define this conditional label!
315             CLog::Log(LOGWARNING, "Label Formating: $VAR[%s] is not defined", params[0].c_str());
316         }
317         else
318           info = g_infoManager.TranslateString(params[0]);
319         CStdString prefix, postfix;
320         if (params.size() > 1)
321           prefix = params[1];
322         if (params.size() > 2)
323           postfix = params[2];
324         m_info.push_back(CInfoPortion(info, prefix, postfix, format == FORMATESCINFO));
325         // and delete it from our work string
326         work = work.substr(pos2 + 1);
327       }
328       else
329       {
330         CLog::Log(LOGERROR, "Error parsing label - missing ']' in \"%s\"", label.c_str());
331         return;
332       }
333     }
334   }
335   while (format != NONE);
336
337   if (!work.empty())
338     m_info.push_back(CInfoPortion(0, work, ""));
339 }
340
341 CGUIInfoLabel::CInfoPortion::CInfoPortion(int info, const CStdString &prefix, const CStdString &postfix, bool escaped /*= false */)
342 {
343   m_info = info;
344   m_prefix = prefix;
345   m_postfix = postfix;
346   m_escaped = escaped;
347   // filter our prefix and postfix for comma's
348   StringUtils::Replace(m_prefix, "$COMMA", ",");
349   StringUtils::Replace(m_postfix, "$COMMA", ",");
350   StringUtils::Replace(m_prefix, "$LBRACKET", "["); StringUtils::Replace(m_prefix, "$RBRACKET", "]");
351   StringUtils::Replace(m_postfix, "$LBRACKET", "["); StringUtils::Replace(m_postfix, "$RBRACKET", "]");
352 }
353
354 CStdString CGUIInfoLabel::CInfoPortion::GetLabel(const CStdString &info) const
355 {
356   CStdString label = m_prefix + info + m_postfix;
357   if (m_escaped) // escape all quotes and backslashes, then quote
358   {
359     StringUtils::Replace(label, "\\", "\\\\");
360     StringUtils::Replace(label, "\"", "\\\"");
361     return "\"" + label + "\"";
362   }
363   return label;
364 }
365
366 CStdString CGUIInfoLabel::GetLabel(const CStdString &label, int contextWindow /*= 0*/, bool preferImage /*= false */)
367 { // translate the label
368   CGUIInfoLabel info(label, "", contextWindow);
369   return info.GetLabel(contextWindow, preferImage);
370 }