Merge pull request #26350 from jjd-uk/estuary_media_align
[xbmc.git] / xbmc / guilib / GUIBaseContainer.cpp
blob70c7a1a2bd9bad73a9992c6398b51a1ef9a14cff
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 "FileItemList.h"
13 #include "GUIInfoManager.h"
14 #include "GUIListItemLayout.h"
15 #include "GUIMessage.h"
16 #include "ServiceBroker.h"
17 #include "guilib/GUIListItem.h"
18 #include "guilib/guiinfo/GUIInfoLabels.h"
19 #include "guilib/listproviders/IListProvider.h"
20 #include "input/actions/Action.h"
21 #include "input/actions/ActionIDs.h"
22 #include "input/keyboard/KeyIDs.h"
23 #include "input/mouse/MouseEvent.h"
24 #include "settings/Settings.h"
25 #include "settings/SettingsComponent.h"
26 #include "utils/CharsetConverter.h"
27 #include "utils/MathUtils.h"
28 #include "utils/SortUtils.h"
29 #include "utils/StringUtils.h"
30 #include "utils/TimeUtils.h"
31 #include "utils/XBMCTinyXML.h"
32 #include "utils/log.h"
34 #include <memory>
36 using namespace KODI;
38 #define HOLD_TIME_START 100
39 #define HOLD_TIME_END 3000
40 #define SCROLLING_GAP 200U
41 #define SCROLLING_THRESHOLD 300U
43 CGUIBaseContainer::CGUIBaseContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
44 : IGUIContainer(parentID, controlID, posX, posY, width, height)
45 , m_scroller(scroller)
47 m_cursor = 0;
48 m_offset = 0;
49 m_lastHoldTime = 0;
50 m_itemsPerPage = 10;
51 m_pageControl = 0;
52 m_orientation = orientation;
53 m_analogScrollCount = 0;
54 m_wasReset = false;
55 m_layout = NULL;
56 m_focusedLayout = NULL;
57 m_cacheItems = preloadItems;
58 m_scrollItemsPerFrame = 0.0f;
59 m_type = VIEW_TYPE_NONE;
60 m_autoScrollMoveTime = 0;
61 m_autoScrollDelayTime = 0;
62 m_autoScrollIsReversed = false;
63 m_lastRenderTime = 0;
66 CGUIBaseContainer::CGUIBaseContainer(const CGUIBaseContainer& other)
67 : IGUIContainer(other),
68 m_renderOffset(other.m_renderOffset),
69 m_analogScrollCount(other.m_analogScrollCount),
70 m_lastHoldTime(other.m_lastHoldTime),
71 m_orientation(other.m_orientation),
72 m_itemsPerPage(other.m_itemsPerPage),
73 m_pageControl(other.m_pageControl),
74 m_layoutCondition(other.m_layoutCondition),
75 m_focusedLayoutCondition(other.m_focusedLayoutCondition),
76 m_scroller(other.m_scroller),
77 m_listProvider(other.m_listProvider ? other.m_listProvider->Clone() : nullptr),
78 m_wasReset(other.m_wasReset),
79 m_letterOffsets(other.m_letterOffsets),
80 m_autoScrollCondition(other.m_autoScrollCondition),
81 m_autoScrollMoveTime(other.m_autoScrollMoveTime),
82 m_autoScrollDelayTime(other.m_autoScrollDelayTime),
83 m_autoScrollIsReversed(other.m_autoScrollIsReversed),
84 m_lastRenderTime(other.m_lastRenderTime),
85 m_cursor(other.m_cursor),
86 m_offset(other.m_offset),
87 m_cacheItems(other.m_cacheItems),
88 m_scrollTimer(other.m_scrollTimer),
89 m_lastScrollStartTimer(other.m_lastScrollStartTimer),
90 m_pageChangeTimer(other.m_pageChangeTimer),
91 m_clickActions(other.m_clickActions),
92 m_focusActions(other.m_focusActions),
93 m_unfocusActions(other.m_unfocusActions),
94 m_matchTimer(other.m_matchTimer),
95 m_match(other.m_match),
96 m_scrollItemsPerFrame(other.m_scrollItemsPerFrame),
97 m_gestureActive(other.m_gestureActive),
98 m_waitForScrollEnd(other.m_waitForScrollEnd),
99 m_lastScrollValue(other.m_lastScrollValue)
101 // Initialize CGUIControl
102 m_bInvalidated = true;
104 for (const auto& item : other.m_items)
105 m_items.emplace_back(std::make_shared<CGUIListItem>(*item));
107 for (const auto& layout : other.m_layouts)
108 m_layouts.emplace_back(layout, this);
110 for (const auto& focusedLayout : other.m_focusedLayouts)
111 m_focusedLayouts.emplace_back(focusedLayout, this);
114 CGUIBaseContainer::~CGUIBaseContainer(void)
116 // release the container from items
117 for (const auto& item : m_items)
118 item->FreeMemory();
121 void CGUIBaseContainer::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
123 CGUIControl::DoProcess(currentTime, dirtyregions);
125 if (m_pageChangeTimer.IsRunning() && m_pageChangeTimer.GetElapsedMilliseconds() > 200)
126 m_pageChangeTimer.Stop();
127 m_wasReset = false;
129 // if not visible, we reset the autoscroll timer
130 if (!IsVisible() && m_autoScrollMoveTime)
132 ResetAutoScrolling();
136 void CGUIBaseContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
138 // update our auto-scrolling as necessary
139 UpdateAutoScrolling(currentTime);
141 if (!m_waitForScrollEnd && !m_gestureActive)
142 ValidateOffset();
144 if (m_bInvalidated)
145 UpdateLayout();
147 if (!m_layout || !m_focusedLayout) return;
149 UpdateScrollOffset(currentTime);
151 if (m_scroller.IsScrolling())
152 MarkDirtyRegion();
154 int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
156 int cacheBefore, cacheAfter;
157 GetCacheOffsets(cacheBefore, cacheAfter);
159 // Free memory not used on screen
160 if ((int)m_items.size() > m_itemsPerPage + cacheBefore + cacheAfter)
161 FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + m_itemsPerPage + 1 + cacheAfter, 0));
163 CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
164 float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
165 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
167 // we offset our draw position to take into account scrolling and whether or not our focused
168 // item is offscreen "above" the list.
169 float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
170 if (GetOffset() + GetCursor() < offset)
171 drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
172 pos += drawOffset;
173 end += cacheAfter * m_layout->Size(m_orientation);
175 int current = offset - cacheBefore;
176 while (pos < end && m_items.size())
178 int itemNo = CorrectOffset(current, 0);
179 if (itemNo >= (int)m_items.size())
180 break;
181 bool focused = (current == GetOffset() + GetCursor());
182 if (itemNo >= 0)
184 std::shared_ptr<CGUIListItem> item = m_items[itemNo];
185 item->SetCurrentItem(itemNo + 1);
187 // render our item
188 if (m_orientation == VERTICAL)
189 ProcessItem(origin.x, pos, item, focused, currentTime, dirtyregions);
190 else
191 ProcessItem(pos, origin.y, item, focused, currentTime, dirtyregions);
193 // increment our position
194 pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
195 current++;
198 // when we are scrolling up, offset will become lower (integer division, see offset calc)
199 // to have same behaviour when scrolling down, we need to set page control to offset+1
200 UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
202 m_lastRenderTime = currentTime;
204 CGUIControl::Process(currentTime, dirtyregions);
207 void CGUIBaseContainer::ProcessItem(float posX,
208 float posY,
209 std::shared_ptr<CGUIListItem>& item,
210 bool focused,
211 unsigned int currentTime,
212 CDirtyRegionList& dirtyregions)
214 if (!m_focusedLayout || !m_layout) return;
216 // set the origin
217 CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
219 if (m_bInvalidated)
220 item->SetInvalid();
221 if (focused)
223 if (!item->GetFocusedLayout())
225 item->SetFocusedLayout(std::make_unique<CGUIListItemLayout>(*m_focusedLayout, this));
227 if (item->GetFocusedLayout())
229 if (item != m_lastItem || !HasFocus())
231 item->GetFocusedLayout()->SetFocusedItem(0);
233 if (item != m_lastItem && HasFocus())
235 item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
236 unsigned int subItem = 1;
237 if (m_lastItem && m_lastItem->GetFocusedLayout())
238 subItem = m_lastItem->GetFocusedLayout()->GetFocusedItem();
239 item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
241 item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
243 m_lastItem = item;
245 else
247 if (item->GetFocusedLayout())
248 item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set
249 if (!item->GetLayout())
251 auto layout = std::make_unique<CGUIListItemLayout>(*m_layout, this);
252 item->SetLayout(std::move(layout));
254 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
255 item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
256 if (item->GetLayout())
257 item->GetLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
260 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
263 void CGUIBaseContainer::Render()
265 if (!m_layout || !m_focusedLayout) return;
267 int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
269 int cacheBefore, cacheAfter;
270 GetCacheOffsets(cacheBefore, cacheAfter);
272 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
274 CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
275 float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
276 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
278 // we offset our draw position to take into account scrolling and whether or not our focused
279 // item is offscreen "above" the list.
280 float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
281 if (GetOffset() + GetCursor() < offset)
282 drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
283 pos += drawOffset;
284 end += cacheAfter * m_layout->Size(m_orientation);
286 float focusedPos = 0;
287 std::shared_ptr<CGUIListItem> focusedItem;
288 int current = offset - cacheBefore;
290 std::vector<RENDERITEM> renderitems;
291 while (pos < end && m_items.size())
293 int itemNo = CorrectOffset(current, 0);
294 if (itemNo >= (int)m_items.size())
295 break;
296 bool focused = (current == GetOffset() + GetCursor());
297 if (itemNo >= 0)
299 std::shared_ptr<CGUIListItem> item = m_items[itemNo];
300 // render our item
301 if (focused)
303 focusedPos = pos;
304 focusedItem = item;
306 else
308 if (m_orientation == VERTICAL)
309 renderitems.emplace_back(RENDERITEM{origin.x, pos, item, false});
310 else
311 renderitems.emplace_back(RENDERITEM{pos, origin.y, item, false});
314 // increment our position
315 pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
316 current++;
318 // render focused item last so it can overlap other items
319 if (focusedItem)
321 if (m_orientation == VERTICAL)
322 renderitems.emplace_back(RENDERITEM{origin.x, focusedPos, focusedItem, true});
323 else
324 renderitems.emplace_back(RENDERITEM{focusedPos, origin.y, focusedItem, true});
327 if (CServiceBroker::GetWinSystem()->GetGfxContext().GetRenderOrder() ==
328 RENDER_ORDER_FRONT_TO_BACK)
330 for (auto it = std::crbegin(renderitems); it != std::crend(renderitems); it++)
332 RenderItem(it->posX, it->posY, it->item.get(), it->focused);
335 else
337 for (const auto& renderitem : renderitems)
339 RenderItem(renderitem.posX, renderitem.posY, renderitem.item.get(), renderitem.focused);
343 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
346 CGUIControl::Render();
350 void CGUIBaseContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
352 if (!m_focusedLayout || !m_layout) return;
354 // set the origin
355 CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
357 if (focused)
359 if (item->GetFocusedLayout())
360 item->GetFocusedLayout()->Render(item, m_parentID);
362 else
364 if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
365 item->GetFocusedLayout()->Render(item, m_parentID);
366 else if (item->GetLayout())
367 item->GetLayout()->Render(item, m_parentID);
369 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
372 bool CGUIBaseContainer::OnAction(const CAction &action)
374 if (action.GetID() == KEY_UNICODE)
376 std::string letter;
377 g_charsetConverter.wToUTF8({action.GetUnicode()}, letter);
378 OnJumpLetter(letter);
379 return true;
381 // stop the timer on any other action
382 m_matchTimer.Stop();
384 switch (action.GetID())
386 case ACTION_MOVE_LEFT:
387 case ACTION_MOVE_RIGHT:
388 case ACTION_MOVE_DOWN:
389 case ACTION_MOVE_UP:
390 case ACTION_NAV_BACK:
391 case ACTION_PREVIOUS_MENU:
393 if (!HasFocus()) return false;
395 if (action.GetHoldTime() > HOLD_TIME_START &&
396 ((m_orientation == VERTICAL && (action.GetID() == ACTION_MOVE_UP || action.GetID() == ACTION_MOVE_DOWN)) ||
397 (m_orientation == HORIZONTAL && (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_RIGHT))))
398 { // action is held down - repeat a number of times
399 float speed = std::min(1.0f, (float)(action.GetHoldTime() - HOLD_TIME_START) / (HOLD_TIME_END - HOLD_TIME_START));
400 unsigned int frameDuration = std::min(CTimeUtils::GetFrameTime() - m_lastHoldTime, 50u); // max 20fps
402 // maximal scroll rate is at least 30 items per second, and at most (item_rows/7) items per second
403 // i.e. timed to take 7 seconds to traverse the list at full speed.
404 // minimal scroll rate is at least 10 items per second
405 float maxSpeed = std::max(frameDuration * 0.001f * 30, frameDuration * 0.001f * GetRows() / 7);
406 float minSpeed = frameDuration * 0.001f * 10;
407 m_scrollItemsPerFrame += std::max(minSpeed, speed*maxSpeed); // accelerate to max speed
408 m_lastHoldTime = CTimeUtils::GetFrameTime();
410 if(m_scrollItemsPerFrame < 1.0f)//not enough hold time accumulated for one step
411 return true;
413 while (m_scrollItemsPerFrame >= 1)
415 if (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_UP)
416 MoveUp(false);
417 else
418 MoveDown(false);
419 m_scrollItemsPerFrame--;
421 return true;
423 else
425 //if HOLD_TIME_START is reached we need
426 //a sane initial value for calculating m_scrollItemsPerPage
427 m_lastHoldTime = CTimeUtils::GetFrameTime();
428 m_scrollItemsPerFrame = 0.0f;
429 return CGUIControl::OnAction(action);
432 case ACTION_CONTEXT_MENU:
433 if (OnContextMenu())
434 return true;
435 break;
436 case ACTION_SHOW_INFO:
437 if (m_listProvider)
439 const int selected = GetSelectedItem();
440 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
442 if (m_listProvider->OnInfo(m_items[selected]))
443 return true;
446 if (OnInfo())
447 return true;
448 else if (action.GetID())
449 return OnClick(action.GetID());
451 return false;
453 case ACTION_PLAYER_PLAY:
454 if (m_listProvider)
456 const int selected = GetSelectedItem();
457 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
459 if (m_listProvider->OnPlay(m_items[selected]))
460 return true;
463 break;
465 case ACTION_FIRST_PAGE:
466 SelectItem(0);
467 return true;
469 case ACTION_LAST_PAGE:
470 if (m_items.size())
471 SelectItem(m_items.size() - 1);
472 return true;
474 case ACTION_NEXT_LETTER:
475 OnNextLetter();
476 return true;
477 case ACTION_PREV_LETTER:
478 OnPrevLetter();
479 return true;
480 case ACTION_JUMP_SMS2:
481 case ACTION_JUMP_SMS3:
482 case ACTION_JUMP_SMS4:
483 case ACTION_JUMP_SMS5:
484 case ACTION_JUMP_SMS6:
485 case ACTION_JUMP_SMS7:
486 case ACTION_JUMP_SMS8:
487 case ACTION_JUMP_SMS9:
488 OnJumpSMS(action.GetID() - ACTION_JUMP_SMS2 + 2);
489 return true;
491 default:
492 break;
494 return action.GetID() && OnClick(action.GetID());
497 bool CGUIBaseContainer::OnMessage(CGUIMessage& message)
499 if (message.GetControlId() == GetID() )
501 if (!m_listProvider)
503 if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
504 { // bind our items
505 Reset();
506 CFileItemList *items = static_cast<CFileItemList*>(message.GetPointer());
507 for (int i = 0; i < items->Size(); i++)
508 m_items.push_back(items->Get(i));
509 UpdateLayout(true); // true to refresh all items
510 UpdateScrollByLetter();
511 SelectItem(message.GetParam1());
512 return true;
514 else if (message.GetMessage() == GUI_MSG_LABEL_RESET)
516 Reset();
517 SetPageControlRange();
518 return true;
521 if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
523 SelectItem(message.GetParam1());
524 return true;
526 else if (message.GetMessage() == GUI_MSG_SETFOCUS)
528 if (message.GetParam1()) // subfocus item is specified, so set the offset appropriately
530 int offset = GetOffset();
531 if (message.GetParam2() && message.GetParam2() == 1)
532 offset = 0;
533 int item = std::min(offset + message.GetParam1() - 1, (int)m_items.size() - 1);
534 SelectItem(item);
537 else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
539 message.SetParam1(GetSelectedItem());
540 return true;
542 else if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
544 if (message.GetSenderId() == m_pageControl && IsVisible())
545 { // update our page if we're visible - not much point otherwise
546 if (message.GetParam1() != GetOffset())
547 m_pageChangeTimer.StartZero();
548 ScrollToOffset(message.GetParam1());
549 return true;
552 else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
553 { // update our list contents
554 for (unsigned int i = 0; i < m_items.size(); ++i)
555 m_items[i]->SetInvalid();
557 else if (message.GetMessage() == GUI_MSG_REFRESH_THUMBS)
559 if (m_listProvider)
560 m_listProvider->FreeResources(true);
562 else if (message.GetMessage() == GUI_MSG_MOVE_OFFSET)
564 int count = message.GetParam1();
565 while (count < 0)
567 MoveUp(true);
568 count++;
570 while (count > 0)
572 MoveDown(true);
573 count--;
575 return true;
578 return CGUIControl::OnMessage(message);
581 void CGUIBaseContainer::OnUp()
583 CGUIAction action = GetAction(ACTION_MOVE_UP);
584 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
585 if (m_orientation == VERTICAL && MoveUp(wrapAround))
586 return;
587 // with horizontal lists it doesn't make much sense to have multiselect labels
588 CGUIControl::OnUp();
591 void CGUIBaseContainer::OnDown()
593 CGUIAction action = GetAction(ACTION_MOVE_DOWN);
594 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
595 if (m_orientation == VERTICAL && MoveDown(wrapAround))
596 return;
597 // with horizontal lists it doesn't make much sense to have multiselect labels
598 CGUIControl::OnDown();
601 void CGUIBaseContainer::OnLeft()
603 CGUIAction action = GetAction(ACTION_MOVE_LEFT);
604 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
605 if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
606 return;
607 else if (m_orientation == VERTICAL)
609 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
610 if (focusedLayout && focusedLayout->MoveLeft())
611 return;
613 CGUIControl::OnLeft();
616 void CGUIBaseContainer::OnRight()
618 CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
619 bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
620 if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
621 return;
622 else if (m_orientation == VERTICAL)
624 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
625 if (focusedLayout && focusedLayout->MoveRight())
626 return;
628 CGUIControl::OnRight();
631 void CGUIBaseContainer::OnNextLetter()
633 int offset = CorrectOffset(GetOffset(), GetCursor());
634 for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
636 if (m_letterOffsets[i].first > offset)
638 SelectItem(m_letterOffsets[i].first);
639 return;
644 void CGUIBaseContainer::OnPrevLetter()
646 int offset = CorrectOffset(GetOffset(), GetCursor());
647 if (!m_letterOffsets.size())
648 return;
649 for (int i = (int)m_letterOffsets.size() - 1; i >= 0; i--)
651 if (m_letterOffsets[i].first < offset)
653 SelectItem(m_letterOffsets[i].first);
654 return;
659 void CGUIBaseContainer::OnJumpLetter(const std::string& letter, bool skip /*=false*/)
661 if (m_matchTimer.GetElapsedMilliseconds() < letter_match_timeout)
662 m_match += letter;
663 else
664 m_match = letter;
666 m_matchTimer.StartZero();
668 // we can't jump through letters if we have none
669 if (0 == m_letterOffsets.size())
670 return;
672 // find the current letter we're focused on
673 unsigned int offset = CorrectOffset(GetOffset(), GetCursor());
674 unsigned int i = (offset + ((skip) ? 1 : 0)) % m_items.size();
677 std::shared_ptr<CGUIListItem> item = m_items[i];
678 std::string label = item->GetLabel();
679 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
680 label = SortUtils::RemoveArticles(label);
681 if (0 == StringUtils::CompareNoCase(label, m_match, m_match.size()))
683 SelectItem(i);
684 return;
686 i = (i+1) % m_items.size();
687 } while (i != offset);
689 // no match found - repeat with a single letter
690 std::wstring wmatch;
691 g_charsetConverter.utf8ToW(m_match, wmatch);
692 if (wmatch.length() > 1)
694 m_match.clear();
695 OnJumpLetter(letter, true);
699 void CGUIBaseContainer::OnJumpSMS(int letter)
701 static const char letterMap[8][6] = { "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
703 // only 2..9 supported
704 if (letter < 2 || letter > 9 || !m_letterOffsets.size())
705 return;
707 const std::string letters = letterMap[letter - 2];
708 // find where we currently are
709 int offset = CorrectOffset(GetOffset(), GetCursor());
710 unsigned int currentLetter = 0;
711 while (currentLetter + 1 < m_letterOffsets.size() && m_letterOffsets[currentLetter + 1].first <= offset)
712 currentLetter++;
714 // now switch to the next letter
715 std::string current = m_letterOffsets[currentLetter].second;
716 size_t startPos = (letters.find(current) + 1) % letters.size();
717 // now jump to letters[startPos], or another one in the same range if possible
718 size_t pos = startPos;
719 while (true)
721 // check if we can jump to this letter
722 for (size_t i = 0; i < m_letterOffsets.size(); i++)
724 if (m_letterOffsets[i].second == letters.substr(pos, 1))
726 SelectItem(m_letterOffsets[i].first);
727 return;
730 pos = (pos + 1) % letters.size();
731 if (pos == startPos)
732 return;
736 bool CGUIBaseContainer::MoveUp(bool wrapAround)
738 return true;
741 bool CGUIBaseContainer::MoveDown(bool wrapAround)
743 return true;
746 // scrolls the said amount
747 void CGUIBaseContainer::Scroll(int amount)
749 ResetAutoScrolling();
750 ScrollToOffset(GetOffset() + amount);
753 int CGUIBaseContainer::GetSelectedItem() const
755 return CorrectOffset(GetOffset(), GetCursor());
758 std::shared_ptr<CGUIListItem> CGUIBaseContainer::GetListItem(int offset, unsigned int flag) const
760 if (!m_items.size() || !m_layout)
761 return std::shared_ptr<CGUIListItem>();
762 int item = GetSelectedItem() + offset;
763 if (flag & INFOFLAG_LISTITEM_POSITION) // use offset from the first item displayed, taking into account scrolling
764 item = CorrectOffset((int)(m_scroller.GetValue() / m_layout->Size(m_orientation)), offset);
766 if (flag & INFOFLAG_LISTITEM_ABSOLUTE) // use offset from the first item
767 item = CorrectOffset(0, offset);
769 if (flag & INFOFLAG_LISTITEM_WRAP)
771 item %= ((int)m_items.size());
772 if (item < 0) item += m_items.size();
773 return m_items[item];
775 else
777 if (item >= 0 && item < (int)m_items.size())
778 return m_items[item];
780 return std::shared_ptr<CGUIListItem>();
783 CGUIListItemLayout *CGUIBaseContainer::GetFocusedLayout() const
785 std::shared_ptr<CGUIListItem> item = GetListItem(0);
786 if (item.get()) return item->GetFocusedLayout();
787 return NULL;
790 bool CGUIBaseContainer::OnMouseOver(const CPoint &point)
792 // select the item under the pointer
793 if (!m_waitForScrollEnd)
794 SelectItemFromPoint(point - CPoint(m_posX, m_posY));
795 return CGUIControl::OnMouseOver(point);
798 EVENT_RESULT CGUIBaseContainer::OnMouseEvent(const CPoint& point, const MOUSE::CMouseEvent& event)
800 if (event.m_id == ACTION_MOUSE_LEFT_CLICK ||
801 event.m_id == ACTION_MOUSE_DOUBLE_CLICK ||
802 event.m_id == ACTION_MOUSE_RIGHT_CLICK)
804 // Cancel touch
805 m_waitForScrollEnd = false;
806 int select = GetSelectedItem();
807 if (SelectItemFromPoint(point - CPoint(m_posX, m_posY)))
809 if (event.m_id != ACTION_MOUSE_RIGHT_CLICK || select == GetSelectedItem())
810 OnClick(event.m_id);
811 return EVENT_RESULT_HANDLED;
814 else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
816 Scroll(-1);
817 return EVENT_RESULT_HANDLED;
819 else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
821 Scroll(1);
822 return EVENT_RESULT_HANDLED;
824 else if (event.m_id == ACTION_GESTURE_NOTIFY)
826 m_waitForScrollEnd = true;
827 m_lastScrollValue = m_scroller.GetValue();
828 return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL : EVENT_RESULT_PAN_VERTICAL;
830 else if (event.m_id == ACTION_GESTURE_BEGIN)
831 { // grab exclusive access
832 m_gestureActive = true;
833 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
834 SendWindowMessage(msg);
835 return EVENT_RESULT_HANDLED;
837 else if (event.m_id == ACTION_GESTURE_PAN)
838 { // do the drag and validate our offset (corrects for end of scroll)
839 m_scroller.SetValue(m_scroller.GetValue() - ((m_orientation == HORIZONTAL) ? event.m_offsetX : event.m_offsetY));
840 float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
841 int offset = MathUtils::round_int(static_cast<double>(m_scroller.GetValue() / size));
842 m_lastScrollStartTimer.Stop();
843 m_scrollTimer.Start();
844 const int absCursor = CorrectOffset(GetOffset(), GetCursor());
845 SetOffset(offset);
846 ValidateOffset();
847 // Notify Application if Inertial scrolling reaches lists end
848 if (m_waitForScrollEnd)
850 if (fabs(m_scroller.GetValue() - m_lastScrollValue) < 0.001f)
852 m_waitForScrollEnd = false;
853 return EVENT_RESULT_UNHANDLED;
855 else
856 m_lastScrollValue = m_scroller.GetValue();
858 else
860 CGUIBaseContainer::SetCursor(absCursor - CorrectOffset(GetOffset(), 0));
862 return EVENT_RESULT_HANDLED;
864 else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
865 { // release exclusive access
866 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
867 SendWindowMessage(msg);
868 m_scrollTimer.Stop();
869 // and compute the nearest offset from this and scroll there
870 float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
871 float offset = m_scroller.GetValue() / size;
872 int toOffset = MathUtils::round_int(static_cast<double>(offset));
873 if (toOffset < offset)
874 SetOffset(toOffset+1);
875 else
876 SetOffset(toOffset-1);
877 ScrollToOffset(toOffset);
878 ValidateOffset();
879 SetCursor(GetCursor());
880 SetFocus(true);
881 m_waitForScrollEnd = false;
882 m_gestureActive = false;
883 return EVENT_RESULT_HANDLED;
885 return EVENT_RESULT_UNHANDLED;
888 bool CGUIBaseContainer::OnClick(int actionID)
890 int subItem = 0;
891 if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
893 if (m_listProvider)
894 { // "select" action
895 int selected = GetSelectedItem();
896 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
898 // One of the actions could trigger a reload of the GUI which destroys
899 // this CGUIBaseContainer and therefore the m_items[selected] we are
900 // going to process. The shared_ptr ensures that item survives until
901 // it has been processed.
902 std::shared_ptr<CGUIListItem> item = m_items[selected];
904 if (m_clickActions.HasActionsMeetingCondition())
905 m_clickActions.ExecuteActions(0, GetParentID(), item);
906 else
907 m_listProvider->OnClick(item);
909 return true;
911 // grab the currently focused subitem (if applicable)
912 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
913 if (focusedLayout)
914 subItem = focusedLayout->GetFocusedItem();
916 else if (actionID == ACTION_MOUSE_RIGHT_CLICK)
918 if (OnContextMenu())
919 return true;
921 // Don't know what to do, so send to our parent window.
922 CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
923 return SendWindowMessage(msg);
926 bool CGUIBaseContainer::OnContextMenu()
928 if (m_listProvider)
930 int selected = GetSelectedItem();
931 if (selected >= 0 && selected < static_cast<int>(m_items.size()))
933 m_listProvider->OnContextMenu(m_items[selected]);
934 return true;
937 return false;
940 std::string CGUIBaseContainer::GetDescription() const
942 std::string strLabel;
943 int item = GetSelectedItem();
944 if (item >= 0 && item < (int)m_items.size())
946 std::shared_ptr<CGUIListItem> pItem = m_items[item];
947 if (pItem->m_bIsFolder)
948 strLabel = StringUtils::Format("[{}]", pItem->GetLabel());
949 else
950 strLabel = pItem->GetLabel();
952 return strLabel;
955 void CGUIBaseContainer::SetFocus(bool bOnOff)
957 if (bOnOff != HasFocus())
959 SetInvalid();
960 m_lastItem.reset();
962 CGUIControl::SetFocus(bOnOff);
965 void CGUIBaseContainer::SaveStates(std::vector<CControlState> &states)
967 if (!m_listProvider || !m_listProvider->AlwaysFocusDefaultItem())
968 states.emplace_back(GetID(), GetSelectedItem());
971 void CGUIBaseContainer::SetPageControl(int id)
973 m_pageControl = id;
976 bool CGUIBaseContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
978 minOffset = 0;
979 maxOffset = GetRows() - m_itemsPerPage;
980 return true;
983 void CGUIBaseContainer::ValidateOffset()
987 void CGUIBaseContainer::AllocResources()
989 CGUIControl::AllocResources();
990 CalculateLayout();
991 if (m_listProvider)
993 UpdateListProvider(true);
997 void CGUIBaseContainer::FreeResources(bool immediately)
999 CGUIControl::FreeResources(immediately);
1000 if (m_listProvider)
1002 if (immediately)
1004 Reset();
1005 m_listProvider->Reset();
1008 m_scroller.Stop();
1011 void CGUIBaseContainer::UpdateLayout(bool updateAllItems)
1013 if (updateAllItems)
1014 { // free memory of items
1015 for (iItems it = m_items.begin(); it != m_items.end(); ++it)
1016 (*it)->FreeMemory();
1018 // and recalculate the layout
1019 CalculateLayout();
1020 SetPageControlRange();
1021 MarkDirtyRegion();
1024 void CGUIBaseContainer::SetPageControlRange()
1026 if (m_pageControl)
1028 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetRows());
1029 SendWindowMessage(msg);
1033 void CGUIBaseContainer::UpdatePageControl(int offset)
1035 if (m_pageControl)
1036 { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
1037 CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, offset);
1038 SendWindowMessage(msg);
1042 void CGUIBaseContainer::UpdateVisibility(const CGUIListItem *item)
1044 CGUIControl::UpdateVisibility(item);
1046 if (!IsVisible() && !CGUIControl::CanFocus())
1047 return; // no need to update the content if we're not visible and we can't focus
1049 // update layouts in case of condition changed
1050 if ((m_layout && m_layout->CheckCondition() != m_layoutCondition) ||
1051 (m_focusedLayout && m_focusedLayout->CheckCondition() != m_focusedLayoutCondition))
1053 if (m_layout)
1054 m_layoutCondition = m_layout->CheckCondition();
1055 if (m_focusedLayout)
1056 m_focusedLayoutCondition = m_focusedLayout->CheckCondition();
1058 int itemIndex = GetSelectedItem();
1059 UpdateLayout(true); // true to refresh all items
1060 SelectItem(itemIndex);
1063 UpdateListProvider();
1066 void CGUIBaseContainer::AssignDepth()
1068 std::shared_ptr<CGUIListItem> focusedItem = nullptr;
1069 int32_t current = 0;
1071 for (const auto& item : m_items)
1073 bool focused = (current == GetOffset() + GetCursor());
1074 if (focused)
1076 focusedItem = item;
1078 else
1080 if (item->GetFocusedLayout())
1081 item->GetFocusedLayout()->AssignDepth();
1082 if (item->GetLayout())
1083 item->GetLayout()->AssignDepth();
1085 current++;
1088 if (focusedItem)
1090 if (focusedItem->GetFocusedLayout())
1091 focusedItem->GetFocusedLayout()->AssignDepth();
1092 if (focusedItem->GetLayout())
1093 focusedItem->GetLayout()->AssignDepth();
1097 void CGUIBaseContainer::UpdateListProvider(bool forceRefresh /* = false */)
1099 if (m_listProvider)
1101 if (m_listProvider->Update(forceRefresh))
1103 // save the current item
1104 int currentItem = GetSelectedItem();
1105 CGUIListItem *current = (currentItem >= 0 && currentItem < (int)m_items.size()) ? m_items[currentItem].get() : NULL;
1106 const std::string prevSelectedPath((current && current->IsFileItem()) ? static_cast<CFileItem *>(current)->GetPath() : "");
1108 Reset();
1109 m_listProvider->Fetch(m_items);
1110 SetPageControlRange();
1111 // update the newly selected item
1112 bool found = false;
1114 // first, try to re-identify selected item by comparing item pointers, though it is not guaranteed that item instances got not recreated on update.
1115 for (int i = 0; i < (int)m_items.size(); i++)
1117 if (m_items[i].get() == current)
1119 found = true;
1120 if (i != currentItem)
1122 SelectItem(i);
1123 break;
1127 if (!found && !prevSelectedPath.empty())
1129 // as fallback, try to re-identify selected item by comparing item paths.
1130 for (int i = 0; i < static_cast<int>(m_items.size()); i++)
1132 const std::shared_ptr<CGUIListItem> c(m_items[i]);
1133 if (c->IsFileItem())
1135 const std::string &selectedPath = static_cast<CFileItem *>(c.get())->GetPath();
1136 if (selectedPath == prevSelectedPath)
1138 found = true;
1139 if (i != currentItem)
1141 SelectItem(i);
1142 break;
1148 if (!found && currentItem >= (int)m_items.size())
1149 SelectItem(m_items.size()-1);
1150 SetInvalid();
1152 // always update the scroll by letter, as the list provider may have altered labels
1153 // while not actually changing the list items.
1154 UpdateScrollByLetter();
1158 void CGUIBaseContainer::CalculateLayout()
1160 CGUIListItemLayout *oldFocusedLayout = m_focusedLayout;
1161 CGUIListItemLayout *oldLayout = m_layout;
1162 GetCurrentLayouts();
1164 // calculate the number of items to display
1165 if (!m_focusedLayout || !m_layout)
1166 return;
1168 if (oldLayout == m_layout && oldFocusedLayout == m_focusedLayout)
1169 return; // nothing has changed, so don't update stuff
1171 m_itemsPerPage = std::max((int)((Size() - m_focusedLayout->Size(m_orientation)) / m_layout->Size(m_orientation)) + 1, 1);
1173 // ensure that the scroll offset is a multiple of our size
1174 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
1177 void CGUIBaseContainer::UpdateScrollByLetter()
1179 m_letterOffsets.clear();
1181 // for scrolling by letter we have an offset table into our vector.
1182 std::string currentMatch;
1183 for (unsigned int i = 0; i < m_items.size(); i++)
1185 std::shared_ptr<CGUIListItem> item = m_items[i];
1186 // The letter offset jumping is only for ASCII characters at present, and
1187 // our checks are all done in uppercase
1188 std::string nextLetter;
1189 std::wstring character = item->GetSortLabel().substr(0, 1);
1190 StringUtils::ToUpper(character);
1191 g_charsetConverter.wToUTF8(character, nextLetter);
1192 if (currentMatch != nextLetter)
1194 currentMatch = nextLetter;
1195 m_letterOffsets.emplace_back(static_cast<int>(i), currentMatch);
1200 unsigned int CGUIBaseContainer::GetRows() const
1202 return m_items.size();
1205 inline float CGUIBaseContainer::Size() const
1207 return (m_orientation == HORIZONTAL) ? m_width : m_height;
1210 int CGUIBaseContainer::ScrollCorrectionRange() const
1212 int range = m_itemsPerPage / 4;
1213 if (range <= 0) range = 1;
1214 return range;
1217 void CGUIBaseContainer::ScrollToOffset(int offset)
1219 int minOffset, maxOffset;
1220 if(GetOffsetRange(minOffset, maxOffset))
1221 offset = std::max(minOffset, std::min(offset, maxOffset));
1222 float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
1223 int range = ScrollCorrectionRange();
1224 if (offset * size < m_scroller.GetValue() && m_scroller.GetValue() - offset * size > size * range)
1225 { // scrolling up, and we're jumping more than 0.5 of a screen
1226 m_scroller.SetValue((offset + range) * size);
1228 if (offset * size > m_scroller.GetValue() && offset * size - m_scroller.GetValue() > size * range)
1229 { // scrolling down, and we're jumping more than 0.5 of a screen
1230 m_scroller.SetValue((offset - range) * size);
1232 m_scroller.ScrollTo(offset * size);
1233 m_lastScrollStartTimer.StartZero();
1234 if (!m_wasReset)
1236 SetContainerMoving(offset - GetOffset());
1237 if (m_scroller.IsScrolling())
1238 m_scrollTimer.Start();
1239 else
1240 m_scrollTimer.Stop();
1242 else
1244 m_scrollTimer.Stop();
1245 m_scroller.Update(~0U);
1247 SetOffset(offset);
1250 void CGUIBaseContainer::SetAutoScrolling(const TiXmlNode *node)
1252 if (!node) return;
1253 const TiXmlElement *scroll = node->FirstChildElement("autoscroll");
1254 if (scroll)
1256 scroll->Attribute("time", &m_autoScrollMoveTime);
1257 if (scroll->Attribute("reverse"))
1258 m_autoScrollIsReversed = true;
1259 if (scroll->FirstChild())
1260 m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(scroll->FirstChild()->ValueStr(), GetParentID());
1264 void CGUIBaseContainer::ResetAutoScrolling()
1266 m_autoScrollDelayTime = 0;
1269 void CGUIBaseContainer::UpdateAutoScrolling(unsigned int currentTime)
1271 if (m_autoScrollCondition && m_autoScrollCondition->Get(INFO::DEFAULT_CONTEXT))
1273 if (m_lastRenderTime)
1274 m_autoScrollDelayTime += currentTime - m_lastRenderTime;
1275 if (m_autoScrollDelayTime > (unsigned int)m_autoScrollMoveTime && !m_scroller.IsScrolling())
1276 { // delay is finished - start moving
1277 m_autoScrollDelayTime = 0;
1278 // Move up or down whether reversed moving is true or false
1279 m_autoScrollIsReversed ? MoveUp(true) : MoveDown(true);
1282 else
1283 ResetAutoScrolling();
1286 void CGUIBaseContainer::SetContainerMoving(int direction)
1288 if (direction)
1289 CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetContainerMoving(GetID(), direction > 0, m_scroller.IsScrolling());
1292 void CGUIBaseContainer::UpdateScrollOffset(unsigned int currentTime)
1294 if (m_scroller.Update(currentTime))
1295 MarkDirtyRegion();
1296 else if (m_lastScrollStartTimer.IsRunning() && m_lastScrollStartTimer.GetElapsedMilliseconds() >= SCROLLING_GAP)
1298 m_scrollTimer.Stop();
1299 m_lastScrollStartTimer.Stop();
1300 SetCursor(GetCursor());
1304 int CGUIBaseContainer::CorrectOffset(int offset, int cursor) const
1306 return offset + cursor;
1309 void CGUIBaseContainer::Reset()
1311 m_wasReset = true;
1312 m_items.clear();
1313 m_lastItem.reset();
1314 ResetAutoScrolling();
1317 void CGUIBaseContainer::LoadLayout(TiXmlElement *layout)
1319 TiXmlElement *itemElement = layout->FirstChildElement("itemlayout");
1320 while (itemElement)
1321 { // we have a new item layout
1322 m_layouts.emplace_back();
1323 m_layouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
1324 itemElement = itemElement->NextSiblingElement("itemlayout");
1325 m_layouts.back().SetParentControl(this);
1327 itemElement = layout->FirstChildElement("focusedlayout");
1328 while (itemElement)
1329 { // we have a new item layout
1330 m_focusedLayouts.emplace_back();
1331 m_focusedLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
1332 itemElement = itemElement->NextSiblingElement("focusedlayout");
1333 m_focusedLayouts.back().SetParentControl(this);
1337 void CGUIBaseContainer::LoadListProvider(TiXmlElement *content, int defaultItem, bool defaultAlways)
1339 m_listProvider = IListProvider::Create(content, GetParentID());
1340 if (m_listProvider)
1341 m_listProvider->SetDefaultItem(defaultItem, defaultAlways);
1344 void CGUIBaseContainer::SetListProvider(std::unique_ptr<IListProvider> provider)
1346 m_listProvider = std::move(provider);
1347 UpdateListProvider(true);
1350 void CGUIBaseContainer::SetRenderOffset(const CPoint &offset)
1352 m_renderOffset = offset;
1355 void CGUIBaseContainer::FreeMemory(int keepStart, int keepEnd)
1357 if (keepStart < keepEnd)
1358 { // remove before keepStart and after keepEnd
1359 for (int i = 0; i < keepStart && i < (int)m_items.size(); ++i)
1360 m_items[i]->FreeMemory();
1361 for (int i = std::max(keepEnd + 1, 0); i < (int)m_items.size(); ++i)
1362 m_items[i]->FreeMemory();
1364 else
1365 { // wrapping
1366 for (int i = std::max(keepEnd + 1, 0); i < keepStart && i < (int)m_items.size(); ++i)
1367 m_items[i]->FreeMemory();
1371 bool CGUIBaseContainer::InsideLayout(const CGUIListItemLayout *layout, const CPoint &point) const
1373 if (!layout) return false;
1374 if ((m_orientation == VERTICAL && (layout->Size(HORIZONTAL) > 1) && point.x > layout->Size(HORIZONTAL)) ||
1375 (m_orientation == HORIZONTAL && (layout->Size(VERTICAL) > 1)&& point.y > layout->Size(VERTICAL)))
1376 return false;
1377 return true;
1380 #ifdef _DEBUG
1381 void CGUIBaseContainer::DumpTextureUse()
1383 CLog::Log(LOGDEBUG, "{} for container {}", __FUNCTION__, GetID());
1384 for (unsigned int i = 0; i < m_items.size(); ++i)
1386 std::shared_ptr<CGUIListItem> item = m_items[i];
1387 if (item->GetFocusedLayout()) item->GetFocusedLayout()->DumpTextureUse();
1388 if (item->GetLayout()) item->GetLayout()->DumpTextureUse();
1391 #endif
1393 bool CGUIBaseContainer::GetCondition(int condition, int data) const
1395 switch (condition)
1397 case CONTAINER_ROW:
1398 return (m_orientation == VERTICAL) ? (GetCursor() == data) : true;
1399 case CONTAINER_COLUMN:
1400 return (m_orientation == HORIZONTAL) ? (GetCursor() == data) : true;
1401 case CONTAINER_POSITION:
1402 return (GetCursor() == data);
1403 case CONTAINER_HAS_NEXT:
1404 return (HasNextPage());
1405 case CONTAINER_HAS_PREVIOUS:
1406 return (HasPreviousPage());
1407 case CONTAINER_HAS_PARENT_ITEM:
1408 return (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder());
1409 case CONTAINER_SUBITEM:
1411 CGUIListItemLayout *layout = GetFocusedLayout();
1412 return layout ? (layout->GetFocusedItem() == (unsigned int)data) : false;
1414 case CONTAINER_SCROLLING:
1415 return ((m_scrollTimer.IsRunning() && m_scrollTimer.GetElapsedMilliseconds() > std::max(m_scroller.GetDuration(), SCROLLING_THRESHOLD)) || m_pageChangeTimer.IsRunning());
1416 case CONTAINER_ISUPDATING:
1417 return (m_listProvider) ? m_listProvider->IsUpdating() : false;
1418 default:
1419 return false;
1423 void CGUIBaseContainer::GetCurrentLayouts()
1425 m_layout = NULL;
1426 for (auto &layout : m_layouts)
1428 if (layout.CheckCondition())
1430 m_layout = &layout;
1431 break;
1434 if (!m_layout && !m_layouts.empty())
1435 m_layout = &m_layouts.front(); // failsafe
1437 m_focusedLayout = NULL;
1438 for (auto &layout : m_focusedLayouts)
1440 if (layout.CheckCondition())
1442 m_focusedLayout = &layout;
1443 break;
1446 if (!m_focusedLayout && !m_focusedLayouts.empty())
1447 m_focusedLayout = &m_focusedLayouts.front(); // failsafe
1450 bool CGUIBaseContainer::HasNextPage() const
1452 return false;
1455 bool CGUIBaseContainer::HasPreviousPage() const
1457 return false;
1460 std::string CGUIBaseContainer::GetLabel(int info) const
1462 std::string label;
1463 switch (info)
1465 case CONTAINER_NUM_PAGES:
1466 label = std::to_string((GetRows() + m_itemsPerPage - 1) / m_itemsPerPage);
1467 break;
1468 case CONTAINER_CURRENT_PAGE:
1469 label = std::to_string(GetCurrentPage());
1470 break;
1471 case CONTAINER_POSITION:
1472 label = std::to_string(GetCursor());
1473 break;
1474 case CONTAINER_CURRENT_ITEM:
1476 if (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
1477 label = std::to_string(GetSelectedItem());
1478 else
1479 label = std::to_string(GetSelectedItem() + 1);
1481 break;
1482 case CONTAINER_NUM_ALL_ITEMS:
1483 case CONTAINER_NUM_ITEMS:
1485 unsigned int numItems = GetNumItems();
1486 if (info == CONTAINER_NUM_ITEMS && numItems && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
1487 label = std::to_string(numItems - 1);
1488 else
1489 label = std::to_string(numItems);
1491 break;
1492 case CONTAINER_NUM_NONFOLDER_ITEMS:
1494 int numItems = 0;
1495 for (const auto& item : m_items)
1497 if (!item->m_bIsFolder)
1498 numItems++;
1500 label = std::to_string(numItems);
1502 break;
1503 default:
1504 break;
1506 return label;
1509 int CGUIBaseContainer::GetCurrentPage() const
1511 if (GetOffset() + m_itemsPerPage >= (int)GetRows()) // last page
1512 return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
1513 return GetOffset() / m_itemsPerPage + 1;
1516 void CGUIBaseContainer::GetCacheOffsets(int &cacheBefore, int &cacheAfter) const
1518 if (m_scroller.IsScrollingDown())
1520 cacheBefore = 0;
1521 cacheAfter = m_cacheItems;
1523 else if (m_scroller.IsScrollingUp())
1525 cacheBefore = m_cacheItems;
1526 cacheAfter = 0;
1528 else
1530 cacheBefore = m_cacheItems / 2;
1531 cacheAfter = m_cacheItems / 2;
1535 void CGUIBaseContainer::SetCursor(int cursor)
1537 if (m_cursor != cursor)
1538 MarkDirtyRegion();
1539 m_cursor = cursor;
1542 void CGUIBaseContainer::SetOffset(int offset)
1544 if (m_offset != offset)
1545 MarkDirtyRegion();
1546 m_offset = offset;
1549 bool CGUIBaseContainer::CanFocus() const
1551 if (CGUIControl::CanFocus())
1554 We allow focus if we have items available or if we have a list provider
1555 that's in the process of updating.
1557 return !m_items.empty() || (m_listProvider && m_listProvider->IsUpdating());
1559 return false;
1562 void CGUIBaseContainer::OnFocus()
1564 if (m_listProvider && m_listProvider->AlwaysFocusDefaultItem())
1565 SelectItem(m_listProvider->GetDefaultItem());
1567 if (m_focusActions.HasAnyActions())
1568 m_focusActions.ExecuteActions(GetID(), GetParentID());
1570 CGUIControl::OnFocus();
1573 void CGUIBaseContainer::OnUnFocus()
1575 if (m_unfocusActions.HasAnyActions())
1576 m_unfocusActions.ExecuteActions(GetID(), GetParentID());
1578 CGUIControl::OnUnFocus();