[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / guilib / GUITextBox.cpp
blob62b00723e780a2f430d1be56c636f4852c3718db
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUITextBox.h"
11 #include "GUIInfoManager.h"
12 #include "GUIMessage.h"
13 #include "guilib/GUIComponent.h"
14 #include "guilib/guiinfo/GUIInfoLabels.h"
15 #include "utils/MathUtils.h"
16 #include "utils/StringUtils.h"
17 #include "utils/XBMCTinyXML.h"
19 #include <algorithm>
21 using namespace KODI::GUILIB;
23 CGUITextBox::CGUITextBox(int parentID, int controlID, float posX, float posY, float width, float height,
24 const CLabelInfo& labelInfo, int scrollTime,
25 const CLabelInfo* labelInfoMono)
26 : CGUIControl(parentID, controlID, posX, posY, width, height)
27 , CGUITextLayout(labelInfo.font, true)
28 , m_label(labelInfo)
30 m_offset = 0;
31 m_scrollOffset = 0;
32 m_scrollSpeed = 0;
33 m_itemsPerPage = 10;
34 m_itemHeight = 10;
35 ControlType = GUICONTROL_TEXTBOX;
36 m_pageControl = 0;
37 m_lastRenderTime = 0;
38 m_scrollTime = scrollTime;
39 m_autoScrollTime = 0;
40 m_autoScrollDelay = 3000;
41 m_autoScrollDelayTime = 0;
42 m_autoScrollRepeatAnim = NULL;
43 m_minHeight = 0;
44 m_renderHeight = height;
45 if (labelInfoMono)
46 SetMonoFont(labelInfoMono->font);
49 CGUITextBox::CGUITextBox(const CGUITextBox& from)
50 : CGUIControl(from), CGUITextLayout(from), m_autoScrollCondition(from.m_autoScrollCondition)
52 m_pageControl = from.m_pageControl;
53 m_scrollTime = from.m_scrollTime;
54 m_autoScrollTime = from.m_autoScrollTime;
55 m_autoScrollDelay = from.m_autoScrollDelay;
56 m_minHeight = from.m_minHeight;
57 m_renderHeight = from.m_renderHeight;
58 m_autoScrollRepeatAnim = NULL;
59 if (from.m_autoScrollRepeatAnim)
60 m_autoScrollRepeatAnim = new CAnimation(*from.m_autoScrollRepeatAnim);
61 m_label = from.m_label;
62 m_info = from.m_info;
63 // defaults
64 m_offset = 0;
65 m_scrollOffset = 0;
66 m_scrollSpeed = 0;
67 m_itemsPerPage = 10;
68 m_itemHeight = 10;
69 m_lastRenderTime = 0;
70 m_autoScrollDelayTime = 0;
71 ControlType = GUICONTROL_TEXTBOX;
74 CGUITextBox::~CGUITextBox(void)
76 delete m_autoScrollRepeatAnim;
77 m_autoScrollRepeatAnim = NULL;
80 bool CGUITextBox::UpdateColors(const CGUIListItem* item)
82 bool changed = CGUIControl::UpdateColors(nullptr);
83 changed |= m_label.UpdateColors();
85 return changed;
88 #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
90 void CGUITextBox::UpdateInfo(const CGUIListItem *item)
92 m_textColor = m_label.textColor;
93 if (!CGUITextLayout::Update(item ? m_info.GetItemLabel(item) : m_info.GetLabel(m_parentID), m_width))
94 return; // nothing changed
96 // needed update, so reset to the top of the textbox and update our sizing/page control
97 SetInvalid();
98 m_offset = 0;
99 m_scrollOffset = 0;
100 ResetAutoScrolling();
102 m_itemHeight = m_font ? m_font->GetLineHeight() : 10;
103 float textHeight = m_font ? m_font->GetTextHeight(m_lines.size()) : m_itemHeight * m_lines.size();
104 float maxHeight = m_height ? m_height : textHeight;
105 m_renderHeight = m_minHeight ? CLAMP(textHeight, m_minHeight, maxHeight) : m_height;
106 m_itemsPerPage = (unsigned int)(m_renderHeight / m_itemHeight);
108 UpdatePageControl();
111 void CGUITextBox::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
113 CGUIControl::DoProcess(currentTime, dirtyregions);
115 // if not visible, we reset the autoscroll timer and positioning
116 if (!IsVisible() && m_autoScrollTime)
118 ResetAutoScrolling();
119 m_lastRenderTime = 0;
120 m_offset = 0;
121 m_scrollOffset = 0;
122 m_scrollSpeed = 0;
126 void CGUITextBox::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
128 // update our auto-scrolling as necessary
129 if (m_autoScrollTime && m_lines.size() > m_itemsPerPage)
131 if (!m_autoScrollCondition || m_autoScrollCondition->Get(INFO::DEFAULT_CONTEXT))
133 if (m_lastRenderTime)
134 m_autoScrollDelayTime += currentTime - m_lastRenderTime;
135 if (m_autoScrollDelayTime > (unsigned int)m_autoScrollDelay && m_scrollSpeed == 0)
136 { // delay is finished - start scrolling
137 MarkDirtyRegion();
138 if (m_offset < (int)m_lines.size() - m_itemsPerPage)
139 ScrollToOffset(m_offset + 1, true);
140 else
141 { // at the end, run a delay and restart
142 if (m_autoScrollRepeatAnim)
144 if (m_autoScrollRepeatAnim->GetState() == ANIM_STATE_NONE)
145 m_autoScrollRepeatAnim->QueueAnimation(ANIM_PROCESS_NORMAL);
146 else if (m_autoScrollRepeatAnim->GetState() == ANIM_STATE_APPLIED)
147 { // reset to the start of the list and start the scrolling again
148 m_offset = 0;
149 m_scrollOffset = 0;
150 ResetAutoScrolling();
156 else if (m_autoScrollCondition)
157 ResetAutoScrolling(); // conditional is false, so reset the autoscrolling
160 // render the repeat anim as appropriate
161 if (m_autoScrollRepeatAnim)
163 if (m_autoScrollRepeatAnim->GetProcess() != ANIM_PROCESS_NONE)
164 MarkDirtyRegion();
165 m_autoScrollRepeatAnim->Animate(currentTime, true);
166 TransformMatrix matrix;
167 m_autoScrollRepeatAnim->RenderAnimation(matrix);
168 m_cachedTextMatrix = CServiceBroker::GetWinSystem()->GetGfxContext().AddTransform(matrix);
171 // update our scroll position as necessary
172 if (m_scrollSpeed != 0)
173 MarkDirtyRegion();
175 if (m_lastRenderTime)
176 m_scrollOffset += m_scrollSpeed * (currentTime - m_lastRenderTime);
177 if ((m_scrollSpeed < 0 && m_scrollOffset < m_offset * m_itemHeight) ||
178 (m_scrollSpeed > 0 && m_scrollOffset > m_offset * m_itemHeight))
180 m_scrollOffset = m_offset * m_itemHeight;
181 m_scrollSpeed = 0;
183 m_lastRenderTime = currentTime;
185 if (m_pageControl)
187 CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl,
188 MathUtils::round_int(static_cast<double>(m_scrollOffset / m_itemHeight)));
189 SendWindowMessage(msg);
192 CGUIControl::Process(currentTime, dirtyregions);
194 if (m_autoScrollRepeatAnim)
195 CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
198 void CGUITextBox::Render()
200 // render the repeat anim as appropriate
201 if (m_autoScrollRepeatAnim)
202 CServiceBroker::GetWinSystem()->GetGfxContext().SetTransform(m_cachedTextMatrix);
204 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_renderHeight))
206 // we offset our draw position to take into account scrolling and whether or not our focused
207 // item is offscreen "above" the list.
208 int offset = (int)(m_scrollOffset / m_itemHeight);
209 float posX = m_posX;
210 float posY = m_posY + offset * m_itemHeight - m_scrollOffset;
212 uint32_t alignment = m_label.align;
214 if (alignment & XBFONT_CENTER_Y)
216 if (m_font)
218 float textHeight = m_font->GetTextHeight(std::min((unsigned int)m_lines.size(), m_itemsPerPage));
220 if (textHeight <= m_renderHeight)
221 posY += (m_renderHeight - textHeight) * 0.5f;
224 alignment &= ~XBFONT_CENTER_Y;
227 // alignment correction
228 if (alignment & XBFONT_CENTER_X)
229 posX += m_width * 0.5f;
230 if (alignment & XBFONT_RIGHT)
231 posX += m_width;
233 if (m_font)
235 m_font->Begin();
236 int current = offset;
238 // set the main text color
239 if (m_colors.size())
240 m_colors[0] = m_label.textColor;
242 while (posY < m_posY + m_renderHeight && current < (int)m_lines.size())
244 const CGUIString& lineString = m_lines[current];
245 float linePosX = posX;
246 uint32_t align = alignment;
248 if (lineString.m_text.size() && lineString.m_carriageReturn)
249 align &= ~XBFONT_JUSTIFIED; // last line of a paragraph shouldn't be justified
251 if (align & XBFONT_RIGHT)
253 // We need to adjust the posX in similar way the CGUILabel recalculate the render rect
254 // see CGUILabel::UpdateRenderRect()
255 linePosX -= GetTextWidth(lineString.m_text);
258 m_font->DrawText(linePosX, posY, m_colors, m_label.shadowColor, lineString.m_text, align,
259 m_width);
260 posY += m_itemHeight;
261 current++;
263 m_font->End();
266 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
268 if (m_autoScrollRepeatAnim)
269 CServiceBroker::GetWinSystem()->GetGfxContext().RemoveTransform();
270 CGUIControl::Render();
273 bool CGUITextBox::OnMessage(CGUIMessage& message)
275 if (message.GetControlId() == GetID())
277 if (message.GetMessage() == GUI_MSG_LABEL_SET)
279 m_offset = 0;
280 m_scrollOffset = 0;
281 ResetAutoScrolling();
282 CGUITextLayout::Reset();
283 m_info.SetLabel(message.GetLabel(), "", GetParentID());
286 if (message.GetMessage() == GUI_MSG_LABEL_RESET)
288 m_offset = 0;
289 m_scrollOffset = 0;
290 ResetAutoScrolling();
291 CGUITextLayout::Reset();
292 UpdatePageControl();
293 SetInvalid();
296 if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
298 if (message.GetSenderId() == m_pageControl)
299 { // update our page
300 Scroll(message.GetParam1());
301 return true;
305 if (message.GetMessage() == GUI_MSG_SET_TYPE)
307 UseMonoFont(message.GetParam1() == 1 ? true : false);
308 return true;
312 return CGUIControl::OnMessage(message);
315 float CGUITextBox::GetHeight() const
317 return m_renderHeight;
320 void CGUITextBox::SetMinHeight(float minHeight)
322 if (m_minHeight != minHeight)
323 SetInvalid();
325 m_minHeight = minHeight;
328 void CGUITextBox::UpdatePageControl()
330 if (m_pageControl)
332 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, m_lines.size());
333 SendWindowMessage(msg);
337 bool CGUITextBox::CanFocus() const
339 return false;
342 void CGUITextBox::SetPageControl(int pageControl)
344 m_pageControl = pageControl;
347 void CGUITextBox::SetInfo(const GUIINFO::CGUIInfoLabel &infoLabel)
349 m_info = infoLabel;
352 void CGUITextBox::Scroll(unsigned int offset)
354 ResetAutoScrolling();
355 if (m_lines.size() <= m_itemsPerPage)
356 return; // no need to scroll
357 if (offset > m_lines.size() - m_itemsPerPage)
358 offset = m_lines.size() - m_itemsPerPage; // on last page
359 ScrollToOffset(offset);
362 void CGUITextBox::ScrollToOffset(int offset, bool autoScroll)
364 m_scrollOffset = m_offset * m_itemHeight;
365 int timeToScroll = autoScroll ? m_autoScrollTime : m_scrollTime;
366 m_scrollSpeed = (offset * m_itemHeight - m_scrollOffset) / timeToScroll;
367 m_offset = offset;
370 void CGUITextBox::SetAutoScrolling(const TiXmlNode *node)
372 if (!node) return;
373 const TiXmlElement *scroll = node->FirstChildElement("autoscroll");
374 if (scroll)
376 scroll->Attribute("delay", &m_autoScrollDelay);
377 scroll->Attribute("time", &m_autoScrollTime);
378 if (scroll->FirstChild())
379 m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(scroll->FirstChild()->ValueStr(), GetParentID());
380 int repeatTime;
381 if (scroll->Attribute("repeat", &repeatTime))
382 m_autoScrollRepeatAnim = new CAnimation(CAnimation::CreateFader(100, 0, repeatTime, 1000));
386 void CGUITextBox::SetAutoScrolling(int delay, int time, int repeatTime, const std::string &condition /* = "" */)
388 m_autoScrollDelay = delay;
389 m_autoScrollTime = time;
390 if (!condition.empty())
391 m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, GetParentID());
392 m_autoScrollRepeatAnim = new CAnimation(CAnimation::CreateFader(100, 0, repeatTime, 1000));
395 void CGUITextBox::ResetAutoScrolling()
397 m_autoScrollDelayTime = 0;
398 if (m_autoScrollRepeatAnim)
399 m_autoScrollRepeatAnim->ResetAnimation();
402 unsigned int CGUITextBox::GetRows() const
404 return m_lines.size();
407 int CGUITextBox::GetNumPages() const
409 return m_itemsPerPage > 0 ? (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage : 0;
412 int CGUITextBox::GetCurrentPage() const
414 if (m_offset + m_itemsPerPage >= GetRows()) // last page
415 return GetNumPages();
416 return m_offset / m_itemsPerPage + 1;
419 std::string CGUITextBox::GetLabel(int info) const
421 std::string label;
422 switch (info)
424 case CONTAINER_NUM_PAGES:
425 label = std::to_string(GetNumPages());
426 break;
427 case CONTAINER_CURRENT_PAGE:
428 label = std::to_string(GetCurrentPage());
429 break;
430 default:
431 break;
433 return label;
436 bool CGUITextBox::GetCondition(int condition, int data) const
438 switch (condition)
440 case CONTAINER_HAS_NEXT:
441 return (GetCurrentPage() < GetNumPages());
442 case CONTAINER_HAS_PREVIOUS:
443 return (GetCurrentPage() > 1);
444 default:
445 return false;
449 std::string CGUITextBox::GetDescription() const
451 return GetText();
454 void CGUITextBox::UpdateVisibility(const CGUIListItem *item)
456 // we have to update the page control when we become visible
457 // as another control may be sharing the same page control when we're
458 // not visible
459 bool wasVisible = IsVisible();
460 CGUIControl::UpdateVisibility(item);
461 if (IsVisible() && !wasVisible)
462 UpdatePageControl();