Merge pull request #4687 from ruuk/textboxgettext
[vuplus_xbmc] / xbmc / guilib / GUITextBox.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 "GUITextBox.h"
22 #include "GUIInfoManager.h"
23 #include "utils/XBMCTinyXML.h"
24 #include "utils/MathUtils.h"
25 #include "utils/StringUtils.h"
26
27 using namespace std;
28
29 CGUITextBox::CGUITextBox(int parentID, int controlID, float posX, float posY, float width, float height,
30                          const CLabelInfo& labelInfo, int scrollTime)
31     : CGUIControl(parentID, controlID, posX, posY, width, height)
32     , CGUITextLayout(labelInfo.font, true)
33     , m_label(labelInfo)
34 {
35   m_offset = 0;
36   m_scrollOffset = 0;
37   m_scrollSpeed = 0;
38   m_itemsPerPage = 10;
39   m_itemHeight = 10;
40   ControlType = GUICONTROL_TEXTBOX;
41   m_pageControl = 0;
42   m_lastRenderTime = 0;
43   m_scrollTime = scrollTime;
44   m_autoScrollTime = 0;
45   m_autoScrollDelay = 3000;
46   m_autoScrollDelayTime = 0;
47   m_autoScrollRepeatAnim = NULL;
48   m_minHeight = 0;
49   m_renderHeight = height;
50 }
51
52 CGUITextBox::CGUITextBox(const CGUITextBox &from)
53 : CGUIControl(from), CGUITextLayout(from)
54 {
55   m_pageControl = from.m_pageControl;
56   m_scrollTime = from.m_scrollTime;
57   m_autoScrollCondition = from.m_autoScrollCondition;
58   m_autoScrollTime = from.m_autoScrollTime;
59   m_autoScrollDelay = from.m_autoScrollDelay;
60   m_minHeight = from.m_minHeight;
61   m_renderHeight = from.m_renderHeight;
62   m_autoScrollRepeatAnim = NULL;
63   if (from.m_autoScrollRepeatAnim)
64     m_autoScrollRepeatAnim = new CAnimation(*from.m_autoScrollRepeatAnim);
65   m_label = from.m_label;
66   m_info = from.m_info;
67   // defaults
68   m_offset = 0;
69   m_scrollOffset = 0;
70   m_scrollSpeed = 0;
71   m_itemsPerPage = 10;
72   m_itemHeight = 10;
73   m_lastRenderTime = 0;
74   m_autoScrollDelayTime = 0;
75   ControlType = GUICONTROL_TEXTBOX;
76 }
77
78 CGUITextBox::~CGUITextBox(void)
79 {
80   delete m_autoScrollRepeatAnim;
81   m_autoScrollRepeatAnim = NULL;
82 }
83
84 bool CGUITextBox::UpdateColors()
85 {
86   bool changed = CGUIControl::UpdateColors();
87   changed |= m_label.UpdateColors();
88
89   return changed;
90 }
91
92 #define CLAMP(x, low, high)  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
93
94 void CGUITextBox::UpdateInfo(const CGUIListItem *item)
95 {
96   m_textColor = m_label.textColor;
97   if (!CGUITextLayout::Update(item ? m_info.GetItemLabel(item) : m_info.GetLabel(m_parentID), m_width))
98     return; // nothing changed
99
100   // needed update, so reset to the top of the textbox and update our sizing/page control
101   SetInvalid();
102   m_offset = 0;
103   m_scrollOffset = 0;
104   ResetAutoScrolling();
105
106   m_itemHeight = m_font ? m_font->GetLineHeight() : 10;
107   float textHeight = m_font ? m_font->GetTextHeight(m_lines.size()) : m_itemHeight * m_lines.size();
108   float maxHeight = m_height ? m_height : textHeight;
109   m_renderHeight = m_minHeight ? CLAMP(textHeight, m_minHeight, maxHeight) : m_height;
110   m_itemsPerPage = (unsigned int)(m_renderHeight / m_itemHeight);
111
112   UpdatePageControl();
113 }
114
115 void CGUITextBox::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
116 {
117   CGUIControl::DoProcess(currentTime, dirtyregions);
118
119   // if not visible, we reset the autoscroll timer and positioning
120   if (!IsVisible() && m_autoScrollTime)
121   {
122     ResetAutoScrolling();
123     m_lastRenderTime = 0;
124     m_offset = 0;
125     m_scrollOffset = 0;
126     m_scrollSpeed = 0;
127   }
128 }
129
130 void CGUITextBox::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
131 {
132   // update our auto-scrolling as necessary
133   if (m_autoScrollTime && m_lines.size() > m_itemsPerPage)
134   {
135     if (!m_autoScrollCondition || m_autoScrollCondition->Get())
136     {
137       if (m_lastRenderTime)
138         m_autoScrollDelayTime += currentTime - m_lastRenderTime;
139       if (m_autoScrollDelayTime > (unsigned int)m_autoScrollDelay && m_scrollSpeed == 0)
140       { // delay is finished - start scrolling
141         MarkDirtyRegion();
142         if (m_offset < (int)m_lines.size() - m_itemsPerPage)
143           ScrollToOffset(m_offset + 1, true);
144         else
145         { // at the end, run a delay and restart
146           if (m_autoScrollRepeatAnim)
147           {
148             if (m_autoScrollRepeatAnim->GetState() == ANIM_STATE_NONE)
149               m_autoScrollRepeatAnim->QueueAnimation(ANIM_PROCESS_NORMAL);
150             else if (m_autoScrollRepeatAnim->GetState() == ANIM_STATE_APPLIED)
151             { // reset to the start of the list and start the scrolling again
152               m_offset = 0;
153               m_scrollOffset = 0;
154               ResetAutoScrolling();
155             }
156           }
157         }
158       }
159     }
160     else if (m_autoScrollCondition)
161       ResetAutoScrolling();  // conditional is false, so reset the autoscrolling
162   }
163
164   // render the repeat anim as appropriate
165   if (m_autoScrollRepeatAnim)
166   {
167     if (m_autoScrollRepeatAnim->GetProcess() != ANIM_PROCESS_NONE)
168       MarkDirtyRegion();
169     m_autoScrollRepeatAnim->Animate(currentTime, true);
170     TransformMatrix matrix;
171     m_autoScrollRepeatAnim->RenderAnimation(matrix);
172     m_cachedTextMatrix = g_graphicsContext.AddTransform(matrix);
173   }
174
175   // update our scroll position as necessary
176   if (m_scrollSpeed != 0)
177     MarkDirtyRegion();
178
179   if (m_lastRenderTime)
180     m_scrollOffset += m_scrollSpeed * (currentTime - m_lastRenderTime);
181   if ((m_scrollSpeed < 0 && m_scrollOffset < m_offset * m_itemHeight) ||
182       (m_scrollSpeed > 0 && m_scrollOffset > m_offset * m_itemHeight))
183   {
184     m_scrollOffset = m_offset * m_itemHeight;
185     m_scrollSpeed = 0;
186   }
187   m_lastRenderTime = currentTime;
188
189   if (m_pageControl)
190   {
191     CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, MathUtils::round_int(m_scrollOffset / m_itemHeight));
192     SendWindowMessage(msg);
193   }
194
195   CGUIControl::Process(currentTime, dirtyregions);
196
197   if (m_autoScrollRepeatAnim)
198     g_graphicsContext.RemoveTransform();
199 }
200
201 void CGUITextBox::Render()
202 {
203   // render the repeat anim as appropriate
204   if (m_autoScrollRepeatAnim)
205     g_graphicsContext.SetTransform(m_cachedTextMatrix);
206
207   if (g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_renderHeight))
208   {
209     // we offset our draw position to take into account scrolling and whether or not our focused
210     // item is offscreen "above" the list.
211     int offset = (int)(m_scrollOffset / m_itemHeight);
212     float posX = m_posX;
213     float posY = m_posY + offset * m_itemHeight - m_scrollOffset;
214
215     // alignment correction
216     if (m_label.align & XBFONT_CENTER_X)
217       posX += m_width * 0.5f;
218     if (m_label.align & XBFONT_RIGHT)
219       posX += m_width;
220
221     if (m_font)
222     {
223       m_font->Begin();
224       int current = offset;
225       while (posY < m_posY + m_renderHeight && current < (int)m_lines.size())
226       {
227         uint32_t align = m_label.align;
228         if (m_lines[current].m_text.size() && m_lines[current].m_carriageReturn)
229           align &= ~XBFONT_JUSTIFIED; // last line of a paragraph shouldn't be justified
230         m_font->DrawText(posX, posY + 2, m_colors, m_label.shadowColor, m_lines[current].m_text, align, m_width);
231         posY += m_itemHeight;
232         current++;
233       }
234       m_font->End();
235     }
236
237     g_graphicsContext.RestoreClipRegion();
238   }
239   if (m_autoScrollRepeatAnim)
240     g_graphicsContext.RemoveTransform();
241   CGUIControl::Render();
242 }
243
244 bool CGUITextBox::OnMessage(CGUIMessage& message)
245 {
246   if (message.GetControlId() == GetID())
247   {
248     if (message.GetMessage() == GUI_MSG_LABEL_SET)
249     {
250       m_offset = 0;
251       m_scrollOffset = 0;
252       ResetAutoScrolling();
253       CGUITextLayout::Reset();
254       m_info.SetLabel(message.GetLabel(), "", GetParentID());
255     }
256
257     if (message.GetMessage() == GUI_MSG_LABEL_RESET)
258     {
259       m_offset = 0;
260       m_scrollOffset = 0;
261       ResetAutoScrolling();
262       CGUITextLayout::Reset();
263       if (m_pageControl)
264       {
265         CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, m_lines.size());
266         SendWindowMessage(msg);
267       }
268     }
269
270     if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
271     {
272       if (message.GetSenderId() == m_pageControl)
273       { // update our page
274         Scroll(message.GetParam1());
275         return true;
276       }
277     }
278   }
279
280   return CGUIControl::OnMessage(message);
281 }
282
283 float CGUITextBox::GetHeight() const
284 {
285   return m_renderHeight;
286 }
287
288 void CGUITextBox::SetMinHeight(float minHeight)
289 {
290   if (m_minHeight != minHeight)
291     SetInvalid();
292   
293   m_minHeight = minHeight;
294 }
295
296 void CGUITextBox::UpdatePageControl()
297 {
298   if (m_pageControl)
299   {
300     CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, m_lines.size());
301     SendWindowMessage(msg);
302   }
303 }
304
305 bool CGUITextBox::CanFocus() const
306 {
307   return false;
308 }
309
310 void CGUITextBox::SetPageControl(int pageControl)
311 {
312   m_pageControl = pageControl;
313 }
314
315 void CGUITextBox::SetInfo(const CGUIInfoLabel &infoLabel)
316 {
317   m_info = infoLabel;
318 }
319
320 void CGUITextBox::Scroll(unsigned int offset)
321 {
322   ResetAutoScrolling();
323   if (m_lines.size() <= m_itemsPerPage)
324     return; // no need to scroll
325   if (offset > m_lines.size() - m_itemsPerPage)
326     offset = m_lines.size() - m_itemsPerPage; // on last page
327   ScrollToOffset(offset);
328 }
329
330 void CGUITextBox::ScrollToOffset(int offset, bool autoScroll)
331 {
332   m_scrollOffset = m_offset * m_itemHeight;
333   int timeToScroll = autoScroll ? m_autoScrollTime : m_scrollTime;
334   m_scrollSpeed = (offset * m_itemHeight - m_scrollOffset) / timeToScroll;
335   m_offset = offset;
336 }
337
338 void CGUITextBox::SetAutoScrolling(const TiXmlNode *node)
339 {
340   if (!node) return;
341   const TiXmlElement *scroll = node->FirstChildElement("autoscroll");
342   if (scroll)
343   {
344     scroll->Attribute("delay", &m_autoScrollDelay);
345     scroll->Attribute("time", &m_autoScrollTime);
346     if (scroll->FirstChild())
347       m_autoScrollCondition = g_infoManager.Register(scroll->FirstChild()->ValueStr(), GetParentID());
348     int repeatTime;
349     if (scroll->Attribute("repeat", &repeatTime))
350       m_autoScrollRepeatAnim = new CAnimation(CAnimation::CreateFader(100, 0, repeatTime, 1000));
351   }
352 }
353
354 void CGUITextBox::ResetAutoScrolling()
355 {
356   m_autoScrollDelayTime = 0;
357   if (m_autoScrollRepeatAnim)
358     m_autoScrollRepeatAnim->ResetAnimation();
359 }
360
361 unsigned int CGUITextBox::GetRows() const
362 {
363   return m_lines.size();
364 }
365
366 int CGUITextBox::GetCurrentPage() const
367 {
368   if (m_offset + m_itemsPerPage >= GetRows())  // last page
369     return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
370   return m_offset / m_itemsPerPage + 1;
371 }
372
373 CStdString CGUITextBox::GetLabel(int info) const
374 {
375   CStdString label;
376   switch (info)
377   {
378   case CONTAINER_NUM_PAGES:
379     label = StringUtils::Format("%u", (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage);
380     break;
381   case CONTAINER_CURRENT_PAGE:
382     label = StringUtils::Format("%u", GetCurrentPage());
383     break;
384   default:
385     break;
386   }
387   return label;
388 }
389
390 CStdString CGUITextBox::GetDescription() const
391 {
392   return GetText();
393 }
394
395 void CGUITextBox::UpdateVisibility(const CGUIListItem *item)
396 {
397   // we have to update the page control when we become visible
398   // as another control may be sharing the same page control when we're
399   // not visible
400   bool wasVisible = IsVisible();
401   CGUIControl::UpdateVisibility(item);
402   if (IsVisible() && !wasVisible)
403     UpdatePageControl();
404 }