[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / guilib / GUIBaseContainer.cpp
blobc22eb71284f27425cadf7f3a37b4cd55e3c28eef
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 "GUIBaseContainer.h"
11 #include "FileItem.h"
12 #include "GUIInfoManager.h"
13 #include "GUIListItemLayout.h"
14 #include "GUIMessage.h"
15 #include "ServiceBroker.h"
16 #include "guilib/guiinfo/GUIInfoLabels.h"
17 #include "guilib/listproviders/IListProvider.h"
18 #include "input/actions/Action.h"
19 #include "input/actions/ActionIDs.h"
20 #include "input/keyboard/KeyIDs.h"
21 #include "input/mouse/MouseEvent.h"
22 #include "settings/Settings.h"
23 #include "settings/SettingsComponent.h"
24 #include "utils/CharsetConverter.h"
25 #include "utils/MathUtils.h"
26 #include "utils/SortUtils.h"
27 #include "utils/StringUtils.h"
28 #include "utils/TimeUtils.h"
29 #include "utils/XBMCTinyXML.h"
30 #include "utils/log.h"
32 #include <memory>
34 using namespace KODI;
36 #define HOLD_TIME_START 100
37 #define HOLD_TIME_END 3000
38 #define SCROLLING_GAP 200U
39 #define SCROLLING_THRESHOLD 300U
41 CGUIBaseContainer::CGUIBaseContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
42 : IGUIContainer(parentID, controlID, posX, posY, width, height)
43 , m_scroller(scroller)
45 m_cursor = 0;
46 m_offset = 0;
47 m_lastHoldTime = 0;
48 m_itemsPerPage = 10;
49 m_pageControl = 0;
50 m_orientation = orientation;
51 m_analogScrollCount = 0;
52 m_wasReset = false;
53 m_layout = NULL;
54 m_focusedLayout = NULL;
55 m_cacheItems = preloadItems;
56 m_scrollItemsPerFrame = 0.0f;
57 m_type = VIEW_TYPE_NONE;
58 m_autoScrollMoveTime = 0;
59 m_autoScrollDelayTime = 0;
60 m_autoScrollIsReversed = false;
61 m_lastRenderTime = 0;
64 CGUIBaseContainer::CGUIBaseContainer(const CGUIBaseContainer& other)
65 : IGUIContainer(other),
66 m_renderOffset(other.m_renderOffset),
67 m_analogScrollCount(other.m_analogScrollCount),
68 m_lastHoldTime(other.m_lastHoldTime),
69 m_orientation(other.m_orientation),
70 m_itemsPerPage(other.m_itemsPerPage),
71 m_pageControl(other.m_pageControl),
72 m_layoutCondition(other.m_layoutCondition),
73 m_focusedLayoutCondition(other.m_focusedLayoutCondition),
74 m_scroller(other.m_scroller),
75 m_listProvider(other.m_listProvider ? other.m_listProvider->Clone() : nullptr),
76 m_wasReset(other.m_wasReset),
77 m_letterOffsets(other.m_letterOffsets),
78 m_autoScrollCondition(other.m_autoScrollCondition),
79 m_autoScrollMoveTime(other.m_autoScrollMoveTime),
80 m_autoScrollDelayTime(other.m_autoScrollDelayTime),
81 m_autoScrollIsReversed(other.m_autoScrollIsReversed),
82 m_lastRenderTime(other.m_lastRenderTime),
83 m_cursor(other.m_cursor),
84 m_offset(other.m_offset),
85 m_cacheItems(other.m_cacheItems),
86 m_scrollTimer(other.m_scrollTimer),
87 m_lastScrollStartTimer(other.m_lastScrollStartTimer),
88 m_pageChangeTimer(other.m_pageChangeTimer),
89 m_clickActions(other.m_clickActions),
90 m_focusActions(other.m_focusActions),
91 m_unfocusActions(other.m_unfocusActions),
92 m_matchTimer(other.m_matchTimer),
93 m_match(other.m_match),
94 m_scrollItemsPerFrame(other.m_scrollItemsPerFrame),
95 m_gestureActive(other.m_gestureActive),
96 m_waitForScrollEnd(other.m_waitForScrollEnd),
97 m_lastScrollValue(other.m_lastScrollValue)
99 // Initialize CGUIControl
100 m_bInvalidated = true;
102 for (const auto& item : other.m_items)
103 m_items.emplace_back(std::make_shared<CGUIListItem>(*item));
105 for (const auto& layout : other.m_layouts)
106 m_layouts.emplace_back(layout, this);
108 for (const auto& focusedLayout : other.m_focusedLayouts)
109 m_focusedLayouts.emplace_back(focusedLayout, this);
112 CGUIBaseContainer::~CGUIBaseContainer(void)
114 // release the container from items
115 for (const auto& item : m_items)
116 item->FreeMemory();
119 void CGUIBaseContainer::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
121 CGUIControl::DoProcess(currentTime, dirtyregions);
123 if (m_pageChangeTimer.IsRunning() && m_pageChangeTimer.GetElapsedMilliseconds() > 200)
124 m_pageChangeTimer.Stop();
125 m_wasReset = false;
127 // if not visible, we reset the autoscroll timer
128 if (!IsVisible() && m_autoScrollMoveTime)
130 ResetAutoScrolling();
134 void CGUIBaseContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
136 // update our auto-scrolling as necessary
137 UpdateAutoScrolling(currentTime);
139 if (!m_waitForScrollEnd && !m_gestureActive)
140 ValidateOffset();
142 if (m_bInvalidated)
143 UpdateLayout();
145 if (!m_layout || !m_focusedLayout) return;
147 UpdateScrollOffset(currentTime);
149 if (m_scroller.IsScrolling())
150 MarkDirtyRegion();
152 int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
154 int cacheBefore, cacheAfter;
155 GetCacheOffsets(cacheBefore, cacheAfter);
157 // Free memory not used on screen
158 if ((int)m_items.size() > m_itemsPerPage + cacheBefore + cacheAfter)
159 FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + m_itemsPerPage + 1 + cacheAfter, 0));
161 CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
162 float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
163 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
165 // we offset our draw position to take into account scrolling and whether or not our focused
166 // item is offscreen "above" the list.
167 float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
168 if (GetOffset() + GetCursor() < offset)
169 drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
170 pos += drawOffset;
171 end += cacheAfter * m_layout->Size(m_orientation);
173 int current = offset - cacheBefore;
174 while (pos < end && m_items.size())
176 int itemNo = CorrectOffset(current, 0);
177 if (itemNo >= (int)m_items.size())
178 break;
179 bool focused = (current == GetOffset() + GetCursor());
180 if (itemNo >= 0)
182 std::shared_ptr<CGUIListItem> item = m_items[itemNo];
183 item->SetCurrentItem(itemNo + 1);
185 // render our item
186 if (m_orientation == VERTICAL)
187 ProcessItem(origin.x, pos, item, focused, currentTime, dirtyregions);
188 else
189 ProcessItem(pos, origin.y, item, focused, currentTime, dirtyregions);
191 // increment our position
192 pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
193 current++;
196 // when we are scrolling up, offset will become lower (integer division, see offset calc)
197 // to have same behaviour when scrolling down, we need to set page control to offset+1
198 UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
200 m_lastRenderTime = currentTime;
202 CGUIControl::Process(currentTime, dirtyregions);
205 void CGUIBaseContainer::ProcessItem(float posX,
206 float posY,
207 std::shared_ptr<CGUIListItem>& item,
208 bool focused,
209 unsigned int currentTime,
210 CDirtyRegionList& dirtyregions)
212 if (!m_focusedLayout || !m_layout) return;
214 // set the origin
215 CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
217 if (m_bInvalidated)
218 item->SetInvalid();
219 if (focused)
221 if (!item->GetFocusedLayout())
223 item->SetFocusedLayout(std::make_unique<CGUIListItemLayout>(*m_focusedLayout, this));
225 if (item->GetFocusedLayout())
227 if (item != m_lastItem || !HasFocus())
229 item->GetFocusedLayout()->SetFocusedItem(0);
231 if (item != m_lastItem && HasFocus())
233 item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
234 unsigned int subItem = 1;
235 if (m_lastItem && m_lastItem->GetFocusedLayout())
236 subItem = m_lastItem->GetFocusedLayout()->GetFocusedItem();
237 item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
239 item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
241 m_lastItem = item;
243 else
245 if (item->GetFocusedLayout())
246 item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set
247 if (!item->GetLayout())
249 auto layout = std::make_unique<CGUIListItemLayout>(*m_layout, this);
250 item->SetLayout(std::move(layout));
252 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
253 item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
254 if (item->GetLayout())
255 item->GetLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
258 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
261 void CGUIBaseContainer::Render()
263 if (!m_layout || !m_focusedLayout) return;
265 int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
267 int cacheBefore, cacheAfter;
268 GetCacheOffsets(cacheBefore, cacheAfter);
270 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
272 CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
273 float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
274 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
276 // we offset our draw position to take into account scrolling and whether or not our focused
277 // item is offscreen "above" the list.
278 float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
279 if (GetOffset() + GetCursor() < offset)
280 drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
281 pos += drawOffset;
282 end += cacheAfter * m_layout->Size(m_orientation);
284 float focusedPos = 0;
285 std::shared_ptr<CGUIListItem> focusedItem;
286 int current = offset - cacheBefore;
287 while (pos < end && m_items.size())
289 int itemNo = CorrectOffset(current, 0);
290 if (itemNo >= (int)m_items.size())
291 break;
292 bool focused = (current == GetOffset() + GetCursor());
293 if (itemNo >= 0)
295 std::shared_ptr<CGUIListItem> item = m_items[itemNo];
296 // render our item
297 if (focused)
299 focusedPos = pos;
300 focusedItem = item;
302 else
304 if (m_orientation == VERTICAL)
305 RenderItem(origin.x, pos, item.get(), false);
306 else
307 RenderItem(pos, origin.y, item.get(), false);
310 // increment our position
311 pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
312 current++;
314 // render focused item last so it can overlap other items
315 if (focusedItem)
317 if (m_orientation == VERTICAL)
318 RenderItem(origin.x, focusedPos, focusedItem.get(), true);
319 else
320 RenderItem(focusedPos, origin.y, focusedItem.get(), true);
323 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
326 CGUIControl::Render();
330 void CGUIBaseContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
332 if (!m_focusedLayout || !m_layout) return;
334 // set the origin
335 CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
337 if (focused)
339 if (item->GetFocusedLayout())
340 item->GetFocusedLayout()->Render(item, m_parentID);
342 else
344 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
345 item->GetFocusedLayout()->Render(item, m_parentID);
346 else if (item->GetLayout())
347 item->GetLayout()->Render(item, m_parentID);
349 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
352 bool CGUIBaseContainer::OnAction(const CAction &action)
354 if (action.GetID() == KEY_UNICODE)
356 std::string letter;
357 g_charsetConverter.wToUTF8({action.GetUnicode()}, letter);
358 OnJumpLetter(letter);
359 return true;
361 // stop the timer on any other action
362 m_matchTimer.Stop();
364 switch (action.GetID())
366 case ACTION_MOVE_LEFT:
367 case ACTION_MOVE_RIGHT:
368 case ACTION_MOVE_DOWN:
369 case ACTION_MOVE_UP:
370 case ACTION_NAV_BACK:
371 case ACTION_PREVIOUS_MENU:
373 if (!HasFocus()) return false;
375 if (action.GetHoldTime() > HOLD_TIME_START &&
376 ((m_orientation == VERTICAL && (action.GetID() == ACTION_MOVE_UP || action.GetID() == ACTION_MOVE_DOWN)) ||
377 (m_orientation == HORIZONTAL && (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_RIGHT))))
378 { // action is held down - repeat a number of times
379 float speed = std::min(1.0f, (float)(action.GetHoldTime() - HOLD_TIME_START) / (HOLD_TIME_END - HOLD_TIME_START));
380 unsigned int frameDuration = std::min(CTimeUtils::GetFrameTime() - m_lastHoldTime, 50u); // max 20fps
382 // maximal scroll rate is at least 30 items per second, and at most (item_rows/7) items per second
383 // i.e. timed to take 7 seconds to traverse the list at full speed.
384 // minimal scroll rate is at least 10 items per second
385 float maxSpeed = std::max(frameDuration * 0.001f * 30, frameDuration * 0.001f * GetRows() / 7);
386 float minSpeed = frameDuration * 0.001f * 10;
387 m_scrollItemsPerFrame += std::max(minSpeed, speed*maxSpeed); // accelerate to max speed
388 m_lastHoldTime = CTimeUtils::GetFrameTime();
390 if(m_scrollItemsPerFrame < 1.0f)//not enough hold time accumulated for one step
391 return true;
393 while (m_scrollItemsPerFrame >= 1)
395 if (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_UP)
396 MoveUp(false);
397 else
398 MoveDown(false);
399 m_scrollItemsPerFrame--;
401 return true;
403 else
405 //if HOLD_TIME_START is reached we need
406 //a sane initial value for calculating m_scrollItemsPerPage
407 m_lastHoldTime = CTimeUtils::GetFrameTime();
408 m_scrollItemsPerFrame = 0.0f;
409 return CGUIControl::OnAction(action);
412 case ACTION_CONTEXT_MENU:
413 if (OnContextMenu())
414 return true;
415 break;
416 case ACTION_SHOW_INFO:
417 if (m_listProvider)
419 const int selected = GetSelectedItem();
420 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
422 if (m_listProvider->OnInfo(m_items[selected]))
423 return true;
426 if (OnInfo())
427 return true;
428 else if (action.GetID())
429 return OnClick(action.GetID());
431 return false;
433 case ACTION_PLAYER_PLAY:
434 if (m_listProvider)
436 const int selected = GetSelectedItem();
437 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
439 if (m_listProvider->OnPlay(m_items[selected]))
440 return true;
443 break;
445 case ACTION_FIRST_PAGE:
446 SelectItem(0);
447 return true;
449 case ACTION_LAST_PAGE:
450 if (m_items.size())
451 SelectItem(m_items.size() - 1);
452 return true;
454 case ACTION_NEXT_LETTER:
455 OnNextLetter();
456 return true;
457 case ACTION_PREV_LETTER:
458 OnPrevLetter();
459 return true;
460 case ACTION_JUMP_SMS2:
461 case ACTION_JUMP_SMS3:
462 case ACTION_JUMP_SMS4:
463 case ACTION_JUMP_SMS5:
464 case ACTION_JUMP_SMS6:
465 case ACTION_JUMP_SMS7:
466 case ACTION_JUMP_SMS8:
467 case ACTION_JUMP_SMS9:
468 OnJumpSMS(action.GetID() - ACTION_JUMP_SMS2 + 2);
469 return true;
471 default:
472 break;
474 return action.GetID() && OnClick(action.GetID());
477 bool CGUIBaseContainer::OnMessage(CGUIMessage& message)
479 if (message.GetControlId() == GetID() )
481 if (!m_listProvider)
483 if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
484 { // bind our items
485 Reset();
486 CFileItemList *items = static_cast<CFileItemList*>(message.GetPointer());
487 for (int i = 0; i < items->Size(); i++)
488 m_items.push_back(items->Get(i));
489 UpdateLayout(true); // true to refresh all items
490 UpdateScrollByLetter();
491 SelectItem(message.GetParam1());
492 return true;
494 else if (message.GetMessage() == GUI_MSG_LABEL_RESET)
496 Reset();
497 SetPageControlRange();
498 return true;
501 if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
503 SelectItem(message.GetParam1());
504 return true;
506 else if (message.GetMessage() == GUI_MSG_SETFOCUS)
508 if (message.GetParam1()) // subfocus item is specified, so set the offset appropriately
510 int offset = GetOffset();
511 if (message.GetParam2() && message.GetParam2() == 1)
512 offset = 0;
513 int item = std::min(offset + message.GetParam1() - 1, (int)m_items.size() - 1);
514 SelectItem(item);
517 else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
519 message.SetParam1(GetSelectedItem());
520 return true;
522 else if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
524 if (message.GetSenderId() == m_pageControl && IsVisible())
525 { // update our page if we're visible - not much point otherwise
526 if (message.GetParam1() != GetOffset())
527 m_pageChangeTimer.StartZero();
528 ScrollToOffset(message.GetParam1());
529 return true;
532 else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
533 { // update our list contents
534 for (unsigned int i = 0; i < m_items.size(); ++i)
535 m_items[i]->SetInvalid();
537 else if (message.GetMessage() == GUI_MSG_REFRESH_THUMBS)
539 if (m_listProvider)
540 m_listProvider->FreeResources(true);
542 else if (message.GetMessage() == GUI_MSG_MOVE_OFFSET)
544 int count = message.GetParam1();
545 while (count < 0)
547 MoveUp(true);
548 count++;
550 while (count > 0)
552 MoveDown(true);
553 count--;
555 return true;
558 return CGUIControl::OnMessage(message);
561 void CGUIBaseContainer::OnUp()
563 CGUIAction action = GetAction(ACTION_MOVE_UP);
564 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
565 if (m_orientation == VERTICAL && MoveUp(wrapAround))
566 return;
567 // with horizontal lists it doesn't make much sense to have multiselect labels
568 CGUIControl::OnUp();
571 void CGUIBaseContainer::OnDown()
573 CGUIAction action = GetAction(ACTION_MOVE_DOWN);
574 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
575 if (m_orientation == VERTICAL && MoveDown(wrapAround))
576 return;
577 // with horizontal lists it doesn't make much sense to have multiselect labels
578 CGUIControl::OnDown();
581 void CGUIBaseContainer::OnLeft()
583 CGUIAction action = GetAction(ACTION_MOVE_LEFT);
584 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
585 if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
586 return;
587 else if (m_orientation == VERTICAL)
589 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
590 if (focusedLayout && focusedLayout->MoveLeft())
591 return;
593 CGUIControl::OnLeft();
596 void CGUIBaseContainer::OnRight()
598 CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
599 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
600 if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
601 return;
602 else if (m_orientation == VERTICAL)
604 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
605 if (focusedLayout && focusedLayout->MoveRight())
606 return;
608 CGUIControl::OnRight();
611 void CGUIBaseContainer::OnNextLetter()
613 int offset = CorrectOffset(GetOffset(), GetCursor());
614 for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
616 if (m_letterOffsets[i].first > offset)
618 SelectItem(m_letterOffsets[i].first);
619 return;
624 void CGUIBaseContainer::OnPrevLetter()
626 int offset = CorrectOffset(GetOffset(), GetCursor());
627 if (!m_letterOffsets.size())
628 return;
629 for (int i = (int)m_letterOffsets.size() - 1; i >= 0; i--)
631 if (m_letterOffsets[i].first < offset)
633 SelectItem(m_letterOffsets[i].first);
634 return;
639 void CGUIBaseContainer::OnJumpLetter(const std::string& letter, bool skip /*=false*/)
641 if (m_matchTimer.GetElapsedMilliseconds() < letter_match_timeout)
642 m_match += letter;
643 else
644 m_match = letter;
646 m_matchTimer.StartZero();
648 // we can't jump through letters if we have none
649 if (0 == m_letterOffsets.size())
650 return;
652 // find the current letter we're focused on
653 unsigned int offset = CorrectOffset(GetOffset(), GetCursor());
654 unsigned int i = (offset + ((skip) ? 1 : 0)) % m_items.size();
657 std::shared_ptr<CGUIListItem> item = m_items[i];
658 std::string label = item->GetLabel();
659 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
660 label = SortUtils::RemoveArticles(label);
661 if (0 == StringUtils::CompareNoCase(label, m_match, m_match.size()))
663 SelectItem(i);
664 return;
666 i = (i+1) % m_items.size();
667 } while (i != offset);
669 // no match found - repeat with a single letter
670 std::wstring wmatch;
671 g_charsetConverter.utf8ToW(m_match, wmatch);
672 if (wmatch.length() > 1)
674 m_match.clear();
675 OnJumpLetter(letter, true);
679 void CGUIBaseContainer::OnJumpSMS(int letter)
681 static const char letterMap[8][6] = { "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
683 // only 2..9 supported
684 if (letter < 2 || letter > 9 || !m_letterOffsets.size())
685 return;
687 const std::string letters = letterMap[letter - 2];
688 // find where we currently are
689 int offset = CorrectOffset(GetOffset(), GetCursor());
690 unsigned int currentLetter = 0;
691 while (currentLetter + 1 < m_letterOffsets.size() && m_letterOffsets[currentLetter + 1].first <= offset)
692 currentLetter++;
694 // now switch to the next letter
695 std::string current = m_letterOffsets[currentLetter].second;
696 size_t startPos = (letters.find(current) + 1) % letters.size();
697 // now jump to letters[startPos], or another one in the same range if possible
698 size_t pos = startPos;
699 while (true)
701 // check if we can jump to this letter
702 for (size_t i = 0; i < m_letterOffsets.size(); i++)
704 if (m_letterOffsets[i].second == letters.substr(pos, 1))
706 SelectItem(m_letterOffsets[i].first);
707 return;
710 pos = (pos + 1) % letters.size();
711 if (pos == startPos)
712 return;
716 bool CGUIBaseContainer::MoveUp(bool wrapAround)
718 return true;
721 bool CGUIBaseContainer::MoveDown(bool wrapAround)
723 return true;
726 // scrolls the said amount
727 void CGUIBaseContainer::Scroll(int amount)
729 ResetAutoScrolling();
730 ScrollToOffset(GetOffset() + amount);
733 int CGUIBaseContainer::GetSelectedItem() const
735 return CorrectOffset(GetOffset(), GetCursor());
738 std::shared_ptr<CGUIListItem> CGUIBaseContainer::GetListItem(int offset, unsigned int flag) const
740 if (!m_items.size() || !m_layout)
741 return std::shared_ptr<CGUIListItem>();
742 int item = GetSelectedItem() + offset;
743 if (flag & INFOFLAG_LISTITEM_POSITION) // use offset from the first item displayed, taking into account scrolling
744 item = CorrectOffset((int)(m_scroller.GetValue() / m_layout->Size(m_orientation)), offset);
746 if (flag & INFOFLAG_LISTITEM_ABSOLUTE) // use offset from the first item
747 item = CorrectOffset(0, offset);
749 if (flag & INFOFLAG_LISTITEM_WRAP)
751 item %= ((int)m_items.size());
752 if (item < 0) item += m_items.size();
753 return m_items[item];
755 else
757 if (item >= 0 && item < (int)m_items.size())
758 return m_items[item];
760 return std::shared_ptr<CGUIListItem>();
763 CGUIListItemLayout *CGUIBaseContainer::GetFocusedLayout() const
765 std::shared_ptr<CGUIListItem> item = GetListItem(0);
766 if (item.get()) return item->GetFocusedLayout();
767 return NULL;
770 bool CGUIBaseContainer::OnMouseOver(const CPoint &point)
772 // select the item under the pointer
773 if (!m_waitForScrollEnd)
774 SelectItemFromPoint(point - CPoint(m_posX, m_posY));
775 return CGUIControl::OnMouseOver(point);
778 EVENT_RESULT CGUIBaseContainer::OnMouseEvent(const CPoint& point, const MOUSE::CMouseEvent& event)
780 if (event.m_id == ACTION_MOUSE_LEFT_CLICK ||
781 event.m_id == ACTION_MOUSE_DOUBLE_CLICK ||
782 event.m_id == ACTION_MOUSE_RIGHT_CLICK)
784 // Cancel touch
785 m_waitForScrollEnd = false;
786 int select = GetSelectedItem();
787 if (SelectItemFromPoint(point - CPoint(m_posX, m_posY)))
789 if (event.m_id != ACTION_MOUSE_RIGHT_CLICK || select == GetSelectedItem())
790 OnClick(event.m_id);
791 return EVENT_RESULT_HANDLED;
794 else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
796 Scroll(-1);
797 return EVENT_RESULT_HANDLED;
799 else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
801 Scroll(1);
802 return EVENT_RESULT_HANDLED;
804 else if (event.m_id == ACTION_GESTURE_NOTIFY)
806 m_waitForScrollEnd = true;
807 m_lastScrollValue = m_scroller.GetValue();
808 return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL : EVENT_RESULT_PAN_VERTICAL;
810 else if (event.m_id == ACTION_GESTURE_BEGIN)
811 { // grab exclusive access
812 m_gestureActive = true;
813 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
814 SendWindowMessage(msg);
815 return EVENT_RESULT_HANDLED;
817 else if (event.m_id == ACTION_GESTURE_PAN)
818 { // do the drag and validate our offset (corrects for end of scroll)
819 m_scroller.SetValue(m_scroller.GetValue() - ((m_orientation == HORIZONTAL) ? event.m_offsetX : event.m_offsetY));
820 float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
821 int offset = MathUtils::round_int(static_cast<double>(m_scroller.GetValue() / size));
822 m_lastScrollStartTimer.Stop();
823 m_scrollTimer.Start();
824 const int absCursor = CorrectOffset(GetOffset(), GetCursor());
825 SetOffset(offset);
826 ValidateOffset();
827 // Notify Application if Inertial scrolling reaches lists end
828 if (m_waitForScrollEnd)
830 if (fabs(m_scroller.GetValue() - m_lastScrollValue) < 0.001f)
832 m_waitForScrollEnd = false;
833 return EVENT_RESULT_UNHANDLED;
835 else
836 m_lastScrollValue = m_scroller.GetValue();
838 else
840 CGUIBaseContainer::SetCursor(absCursor - CorrectOffset(GetOffset(), 0));
842 return EVENT_RESULT_HANDLED;
844 else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
845 { // release exclusive access
846 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
847 SendWindowMessage(msg);
848 m_scrollTimer.Stop();
849 // and compute the nearest offset from this and scroll there
850 float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
851 float offset = m_scroller.GetValue() / size;
852 int toOffset = MathUtils::round_int(static_cast<double>(offset));
853 if (toOffset < offset)
854 SetOffset(toOffset+1);
855 else
856 SetOffset(toOffset-1);
857 ScrollToOffset(toOffset);
858 ValidateOffset();
859 SetCursor(GetCursor());
860 SetFocus(true);
861 m_waitForScrollEnd = false;
862 m_gestureActive = false;
863 return EVENT_RESULT_HANDLED;
865 return EVENT_RESULT_UNHANDLED;
868 bool CGUIBaseContainer::OnClick(int actionID)
870 int subItem = 0;
871 if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
873 if (m_listProvider)
874 { // "select" action
875 int selected = GetSelectedItem();
876 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
878 if (m_clickActions.HasActionsMeetingCondition())
879 m_clickActions.ExecuteActions(0, GetParentID(), m_items[selected]);
880 else
881 m_listProvider->OnClick(m_items[selected]);
883 return true;
885 // grab the currently focused subitem (if applicable)
886 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
887 if (focusedLayout)
888 subItem = focusedLayout->GetFocusedItem();
890 else if (actionID == ACTION_MOUSE_RIGHT_CLICK)
892 if (OnContextMenu())
893 return true;
895 // Don't know what to do, so send to our parent window.
896 CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
897 return SendWindowMessage(msg);
900 bool CGUIBaseContainer::OnContextMenu()
902 if (m_listProvider)
904 int selected = GetSelectedItem();
905 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
907 m_listProvider->OnContextMenu(m_items[selected]);
908 return true;
911 return false;
914 std::string CGUIBaseContainer::GetDescription() const
916 std::string strLabel;
917 int item = GetSelectedItem();
918 if (item >= 0 && item < (int)m_items.size())
920 std::shared_ptr<CGUIListItem> pItem = m_items[item];
921 if (pItem->m_bIsFolder)
922 strLabel = StringUtils::Format("[{}]", pItem->GetLabel());
923 else
924 strLabel = pItem->GetLabel();
926 return strLabel;
929 void CGUIBaseContainer::SetFocus(bool bOnOff)
931 if (bOnOff != HasFocus())
933 SetInvalid();
934 m_lastItem.reset();
936 CGUIControl::SetFocus(bOnOff);
939 void CGUIBaseContainer::SaveStates(std::vector<CControlState> &states)
941 if (!m_listProvider || !m_listProvider->AlwaysFocusDefaultItem())
942 states.emplace_back(GetID(), GetSelectedItem());
945 void CGUIBaseContainer::SetPageControl(int id)
947 m_pageControl = id;
950 bool CGUIBaseContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
952 minOffset = 0;
953 maxOffset = GetRows() - m_itemsPerPage;
954 return true;
957 void CGUIBaseContainer::ValidateOffset()
961 void CGUIBaseContainer::AllocResources()
963 CGUIControl::AllocResources();
964 CalculateLayout();
965 if (m_listProvider)
967 UpdateListProvider(true);
971 void CGUIBaseContainer::FreeResources(bool immediately)
973 CGUIControl::FreeResources(immediately);
974 if (m_listProvider)
976 if (immediately)
978 Reset();
979 m_listProvider->Reset();
982 m_scroller.Stop();
985 void CGUIBaseContainer::UpdateLayout(bool updateAllItems)
987 if (updateAllItems)
988 { // free memory of items
989 for (iItems it = m_items.begin(); it != m_items.end(); ++it)
990 (*it)->FreeMemory();
992 // and recalculate the layout
993 CalculateLayout();
994 SetPageControlRange();
995 MarkDirtyRegion();
998 void CGUIBaseContainer::SetPageControlRange()
1000 if (m_pageControl)
1002 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetRows());
1003 SendWindowMessage(msg);
1007 void CGUIBaseContainer::UpdatePageControl(int offset)
1009 if (m_pageControl)
1010 { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
1011 CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, offset);
1012 SendWindowMessage(msg);
1016 void CGUIBaseContainer::UpdateVisibility(const CGUIListItem *item)
1018 CGUIControl::UpdateVisibility(item);
1020 if (!IsVisible() && !CGUIControl::CanFocus())
1021 return; // no need to update the content if we're not visible and we can't focus
1023 // update layouts in case of condition changed
1024 if ((m_layout && m_layout->CheckCondition() != m_layoutCondition) ||
1025 (m_focusedLayout && m_focusedLayout->CheckCondition() != m_focusedLayoutCondition))
1027 if (m_layout)
1028 m_layoutCondition = m_layout->CheckCondition();
1029 if (m_focusedLayout)
1030 m_focusedLayoutCondition = m_focusedLayout->CheckCondition();
1032 int itemIndex = GetSelectedItem();
1033 UpdateLayout(true); // true to refresh all items
1034 SelectItem(itemIndex);
1037 UpdateListProvider();
1040 void CGUIBaseContainer::UpdateListProvider(bool forceRefresh /* = false */)
1042 if (m_listProvider)
1044 if (m_listProvider->Update(forceRefresh))
1046 // save the current item
1047 int currentItem = GetSelectedItem();
1048 CGUIListItem *current = (currentItem >= 0 && currentItem < (int)m_items.size()) ? m_items[currentItem].get() : NULL;
1049 const std::string prevSelectedPath((current && current->IsFileItem()) ? static_cast<CFileItem *>(current)->GetPath() : "");
1051 Reset();
1052 m_listProvider->Fetch(m_items);
1053 SetPageControlRange();
1054 // update the newly selected item
1055 bool found = false;
1057 // first, try to re-identify selected item by comparing item pointers, though it is not guaranteed that item instances got not recreated on update.
1058 for (int i = 0; i < (int)m_items.size(); i++)
1060 if (m_items[i].get() == current)
1062 found = true;
1063 if (i != currentItem)
1065 SelectItem(i);
1066 break;
1070 if (!found && !prevSelectedPath.empty())
1072 // as fallback, try to re-identify selected item by comparing item paths.
1073 for (int i = 0; i < static_cast<int>(m_items.size()); i++)
1075 const std::shared_ptr<CGUIListItem> c(m_items[i]);
1076 if (c->IsFileItem())
1078 const std::string &selectedPath = static_cast<CFileItem *>(c.get())->GetPath();
1079 if (selectedPath == prevSelectedPath)
1081 found = true;
1082 if (i != currentItem)
1084 SelectItem(i);
1085 break;
1091 if (!found && currentItem >= (int)m_items.size())
1092 SelectItem(m_items.size()-1);
1093 SetInvalid();
1095 // always update the scroll by letter, as the list provider may have altered labels
1096 // while not actually changing the list items.
1097 UpdateScrollByLetter();
1101 void CGUIBaseContainer::CalculateLayout()
1103 CGUIListItemLayout *oldFocusedLayout = m_focusedLayout;
1104 CGUIListItemLayout *oldLayout = m_layout;
1105 GetCurrentLayouts();
1107 // calculate the number of items to display
1108 if (!m_focusedLayout || !m_layout)
1109 return;
1111 if (oldLayout == m_layout && oldFocusedLayout == m_focusedLayout)
1112 return; // nothing has changed, so don't update stuff
1114 m_itemsPerPage = std::max((int)((Size() - m_focusedLayout->Size(m_orientation)) / m_layout->Size(m_orientation)) + 1, 1);
1116 // ensure that the scroll offset is a multiple of our size
1117 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
1120 void CGUIBaseContainer::UpdateScrollByLetter()
1122 m_letterOffsets.clear();
1124 // for scrolling by letter we have an offset table into our vector.
1125 std::string currentMatch;
1126 for (unsigned int i = 0; i < m_items.size(); i++)
1128 std::shared_ptr<CGUIListItem> item = m_items[i];
1129 // The letter offset jumping is only for ASCII characters at present, and
1130 // our checks are all done in uppercase
1131 std::string nextLetter;
1132 std::wstring character = item->GetSortLabel().substr(0, 1);
1133 StringUtils::ToUpper(character);
1134 g_charsetConverter.wToUTF8(character, nextLetter);
1135 if (currentMatch != nextLetter)
1137 currentMatch = nextLetter;
1138 m_letterOffsets.emplace_back(static_cast<int>(i), currentMatch);
1143 unsigned int CGUIBaseContainer::GetRows() const
1145 return m_items.size();
1148 inline float CGUIBaseContainer::Size() const
1150 return (m_orientation == HORIZONTAL) ? m_width : m_height;
1153 int CGUIBaseContainer::ScrollCorrectionRange() const
1155 int range = m_itemsPerPage / 4;
1156 if (range <= 0) range = 1;
1157 return range;
1160 void CGUIBaseContainer::ScrollToOffset(int offset)
1162 int minOffset, maxOffset;
1163 if(GetOffsetRange(minOffset, maxOffset))
1164 offset = std::max(minOffset, std::min(offset, maxOffset));
1165 float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
1166 int range = ScrollCorrectionRange();
1167 if (offset * size < m_scroller.GetValue() && m_scroller.GetValue() - offset * size > size * range)
1168 { // scrolling up, and we're jumping more than 0.5 of a screen
1169 m_scroller.SetValue((offset + range) * size);
1171 if (offset * size > m_scroller.GetValue() && offset * size - m_scroller.GetValue() > size * range)
1172 { // scrolling down, and we're jumping more than 0.5 of a screen
1173 m_scroller.SetValue((offset - range) * size);
1175 m_scroller.ScrollTo(offset * size);
1176 m_lastScrollStartTimer.StartZero();
1177 if (!m_wasReset)
1179 SetContainerMoving(offset - GetOffset());
1180 if (m_scroller.IsScrolling())
1181 m_scrollTimer.Start();
1182 else
1183 m_scrollTimer.Stop();
1185 else
1187 m_scrollTimer.Stop();
1188 m_scroller.Update(~0U);
1190 SetOffset(offset);
1193 void CGUIBaseContainer::SetAutoScrolling(const TiXmlNode *node)
1195 if (!node) return;
1196 const TiXmlElement *scroll = node->FirstChildElement("autoscroll");
1197 if (scroll)
1199 scroll->Attribute("time", &m_autoScrollMoveTime);
1200 if (scroll->Attribute("reverse"))
1201 m_autoScrollIsReversed = true;
1202 if (scroll->FirstChild())
1203 m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(scroll->FirstChild()->ValueStr(), GetParentID());
1207 void CGUIBaseContainer::ResetAutoScrolling()
1209 m_autoScrollDelayTime = 0;
1212 void CGUIBaseContainer::UpdateAutoScrolling(unsigned int currentTime)
1214 if (m_autoScrollCondition && m_autoScrollCondition->Get(INFO::DEFAULT_CONTEXT))
1216 if (m_lastRenderTime)
1217 m_autoScrollDelayTime += currentTime - m_lastRenderTime;
1218 if (m_autoScrollDelayTime > (unsigned int)m_autoScrollMoveTime && !m_scroller.IsScrolling())
1219 { // delay is finished - start moving
1220 m_autoScrollDelayTime = 0;
1221 // Move up or down whether reversed moving is true or false
1222 m_autoScrollIsReversed ? MoveUp(true) : MoveDown(true);
1225 else
1226 ResetAutoScrolling();
1229 void CGUIBaseContainer::SetContainerMoving(int direction)
1231 if (direction)
1232 CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetContainerMoving(GetID(), direction > 0, m_scroller.IsScrolling());
1235 void CGUIBaseContainer::UpdateScrollOffset(unsigned int currentTime)
1237 if (m_scroller.Update(currentTime))
1238 MarkDirtyRegion();
1239 else if (m_lastScrollStartTimer.IsRunning() && m_lastScrollStartTimer.GetElapsedMilliseconds() >= SCROLLING_GAP)
1241 m_scrollTimer.Stop();
1242 m_lastScrollStartTimer.Stop();
1243 SetCursor(GetCursor());
1247 int CGUIBaseContainer::CorrectOffset(int offset, int cursor) const
1249 return offset + cursor;
1252 void CGUIBaseContainer::Reset()
1254 m_wasReset = true;
1255 m_items.clear();
1256 m_lastItem.reset();
1257 ResetAutoScrolling();
1260 void CGUIBaseContainer::LoadLayout(TiXmlElement *layout)
1262 TiXmlElement *itemElement = layout->FirstChildElement("itemlayout");
1263 while (itemElement)
1264 { // we have a new item layout
1265 m_layouts.emplace_back();
1266 m_layouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
1267 itemElement = itemElement->NextSiblingElement("itemlayout");
1268 m_layouts.back().SetParentControl(this);
1270 itemElement = layout->FirstChildElement("focusedlayout");
1271 while (itemElement)
1272 { // we have a new item layout
1273 m_focusedLayouts.emplace_back();
1274 m_focusedLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
1275 itemElement = itemElement->NextSiblingElement("focusedlayout");
1276 m_focusedLayouts.back().SetParentControl(this);
1280 void CGUIBaseContainer::LoadListProvider(TiXmlElement *content, int defaultItem, bool defaultAlways)
1282 m_listProvider = IListProvider::Create(content, GetParentID());
1283 if (m_listProvider)
1284 m_listProvider->SetDefaultItem(defaultItem, defaultAlways);
1287 void CGUIBaseContainer::SetListProvider(std::unique_ptr<IListProvider> provider)
1289 m_listProvider = std::move(provider);
1290 UpdateListProvider(true);
1293 void CGUIBaseContainer::SetRenderOffset(const CPoint &offset)
1295 m_renderOffset = offset;
1298 void CGUIBaseContainer::FreeMemory(int keepStart, int keepEnd)
1300 if (keepStart < keepEnd)
1301 { // remove before keepStart and after keepEnd
1302 for (int i = 0; i < keepStart && i < (int)m_items.size(); ++i)
1303 m_items[i]->FreeMemory();
1304 for (int i = std::max(keepEnd + 1, 0); i < (int)m_items.size(); ++i)
1305 m_items[i]->FreeMemory();
1307 else
1308 { // wrapping
1309 for (int i = std::max(keepEnd + 1, 0); i < keepStart && i < (int)m_items.size(); ++i)
1310 m_items[i]->FreeMemory();
1314 bool CGUIBaseContainer::InsideLayout(const CGUIListItemLayout *layout, const CPoint &point) const
1316 if (!layout) return false;
1317 if ((m_orientation == VERTICAL && (layout->Size(HORIZONTAL) > 1) && point.x > layout->Size(HORIZONTAL)) ||
1318 (m_orientation == HORIZONTAL && (layout->Size(VERTICAL) > 1)&& point.y > layout->Size(VERTICAL)))
1319 return false;
1320 return true;
1323 #ifdef _DEBUG
1324 void CGUIBaseContainer::DumpTextureUse()
1326 CLog::Log(LOGDEBUG, "{} for container {}", __FUNCTION__, GetID());
1327 for (unsigned int i = 0; i < m_items.size(); ++i)
1329 std::shared_ptr<CGUIListItem> item = m_items[i];
1330 if (item->GetFocusedLayout()) item->GetFocusedLayout()->DumpTextureUse();
1331 if (item->GetLayout()) item->GetLayout()->DumpTextureUse();
1334 #endif
1336 bool CGUIBaseContainer::GetCondition(int condition, int data) const
1338 switch (condition)
1340 case CONTAINER_ROW:
1341 return (m_orientation == VERTICAL) ? (GetCursor() == data) : true;
1342 case CONTAINER_COLUMN:
1343 return (m_orientation == HORIZONTAL) ? (GetCursor() == data) : true;
1344 case CONTAINER_POSITION:
1345 return (GetCursor() == data);
1346 case CONTAINER_HAS_NEXT:
1347 return (HasNextPage());
1348 case CONTAINER_HAS_PREVIOUS:
1349 return (HasPreviousPage());
1350 case CONTAINER_HAS_PARENT_ITEM:
1351 return (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder());
1352 case CONTAINER_SUBITEM:
1354 CGUIListItemLayout *layout = GetFocusedLayout();
1355 return layout ? (layout->GetFocusedItem() == (unsigned int)data) : false;
1357 case CONTAINER_SCROLLING:
1358 return ((m_scrollTimer.IsRunning() && m_scrollTimer.GetElapsedMilliseconds() > std::max(m_scroller.GetDuration(), SCROLLING_THRESHOLD)) || m_pageChangeTimer.IsRunning());
1359 case CONTAINER_ISUPDATING:
1360 return (m_listProvider) ? m_listProvider->IsUpdating() : false;
1361 default:
1362 return false;
1366 void CGUIBaseContainer::GetCurrentLayouts()
1368 m_layout = NULL;
1369 for (auto &layout : m_layouts)
1371 if (layout.CheckCondition())
1373 m_layout = &layout;
1374 break;
1377 if (!m_layout && !m_layouts.empty())
1378 m_layout = &m_layouts.front(); // failsafe
1380 m_focusedLayout = NULL;
1381 for (auto &layout : m_focusedLayouts)
1383 if (layout.CheckCondition())
1385 m_focusedLayout = &layout;
1386 break;
1389 if (!m_focusedLayout && !m_focusedLayouts.empty())
1390 m_focusedLayout = &m_focusedLayouts.front(); // failsafe
1393 bool CGUIBaseContainer::HasNextPage() const
1395 return false;
1398 bool CGUIBaseContainer::HasPreviousPage() const
1400 return false;
1403 std::string CGUIBaseContainer::GetLabel(int info) const
1405 std::string label;
1406 switch (info)
1408 case CONTAINER_NUM_PAGES:
1409 label = std::to_string((GetRows() + m_itemsPerPage - 1) / m_itemsPerPage);
1410 break;
1411 case CONTAINER_CURRENT_PAGE:
1412 label = std::to_string(GetCurrentPage());
1413 break;
1414 case CONTAINER_POSITION:
1415 label = std::to_string(GetCursor());
1416 break;
1417 case CONTAINER_CURRENT_ITEM:
1419 if (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
1420 label = std::to_string(GetSelectedItem());
1421 else
1422 label = std::to_string(GetSelectedItem() + 1);
1424 break;
1425 case CONTAINER_NUM_ALL_ITEMS:
1426 case CONTAINER_NUM_ITEMS:
1428 unsigned int numItems = GetNumItems();
1429 if (info == CONTAINER_NUM_ITEMS && numItems && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
1430 label = std::to_string(numItems - 1);
1431 else
1432 label = std::to_string(numItems);
1434 break;
1435 case CONTAINER_NUM_NONFOLDER_ITEMS:
1437 int numItems = 0;
1438 for (const auto& item : m_items)
1440 if (!item->m_bIsFolder)
1441 numItems++;
1443 label = std::to_string(numItems);
1445 break;
1446 default:
1447 break;
1449 return label;
1452 int CGUIBaseContainer::GetCurrentPage() const
1454 if (GetOffset() + m_itemsPerPage >= (int)GetRows()) // last page
1455 return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
1456 return GetOffset() / m_itemsPerPage + 1;
1459 void CGUIBaseContainer::GetCacheOffsets(int &cacheBefore, int &cacheAfter) const
1461 if (m_scroller.IsScrollingDown())
1463 cacheBefore = 0;
1464 cacheAfter = m_cacheItems;
1466 else if (m_scroller.IsScrollingUp())
1468 cacheBefore = m_cacheItems;
1469 cacheAfter = 0;
1471 else
1473 cacheBefore = m_cacheItems / 2;
1474 cacheAfter = m_cacheItems / 2;
1478 void CGUIBaseContainer::SetCursor(int cursor)
1480 if (m_cursor != cursor)
1481 MarkDirtyRegion();
1482 m_cursor = cursor;
1485 void CGUIBaseContainer::SetOffset(int offset)
1487 if (m_offset != offset)
1488 MarkDirtyRegion();
1489 m_offset = offset;
1492 bool CGUIBaseContainer::CanFocus() const
1494 if (CGUIControl::CanFocus())
1497 We allow focus if we have items available or if we have a list provider
1498 that's in the process of updating.
1500 return !m_items.empty() || (m_listProvider && m_listProvider->IsUpdating());
1502 return false;
1505 void CGUIBaseContainer::OnFocus()
1507 if (m_listProvider && m_listProvider->AlwaysFocusDefaultItem())
1508 SelectItem(m_listProvider->GetDefaultItem());
1510 if (m_focusActions.HasAnyActions())
1511 m_focusActions.ExecuteActions(GetID(), GetParentID());
1513 CGUIControl::OnFocus();
1516 void CGUIBaseContainer::OnUnFocus()
1518 if (m_unfocusActions.HasAnyActions())
1519 m_unfocusActions.ExecuteActions(GetID(), GetParentID());
1521 CGUIControl::OnUnFocus();