summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/GUIBaseContainer.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/guilib/GUIBaseContainer.cpp
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--xbmc/guilib/GUIBaseContainer.cpp1510
1 files changed, 1510 insertions, 0 deletions
diff --git a/xbmc/guilib/GUIBaseContainer.cpp b/xbmc/guilib/GUIBaseContainer.cpp
new file mode 100644
index 0000000..f8089da
--- /dev/null
+++ b/xbmc/guilib/GUIBaseContainer.cpp
@@ -0,0 +1,1510 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIBaseContainer.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "GUIListItemLayout.h"
+#include "GUIMessage.h"
+#include "ServiceBroker.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+#include "listproviders/IListProvider.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/CharsetConverter.h"
+#include "utils/MathUtils.h"
+#include "utils/SortUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/TimeUtils.h"
+#include "utils/XBMCTinyXML.h"
+#include "utils/log.h"
+
+#define HOLD_TIME_START 100
+#define HOLD_TIME_END 3000
+#define SCROLLING_GAP 200U
+#define SCROLLING_THRESHOLD 300U
+
+CGUIBaseContainer::CGUIBaseContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
+ : IGUIContainer(parentID, controlID, posX, posY, width, height)
+ , m_scroller(scroller)
+{
+ m_cursor = 0;
+ m_offset = 0;
+ m_lastHoldTime = 0;
+ m_itemsPerPage = 10;
+ m_pageControl = 0;
+ m_orientation = orientation;
+ m_analogScrollCount = 0;
+ m_wasReset = false;
+ m_layout = NULL;
+ m_focusedLayout = NULL;
+ m_cacheItems = preloadItems;
+ m_scrollItemsPerFrame = 0.0f;
+ m_type = VIEW_TYPE_NONE;
+ m_autoScrollMoveTime = 0;
+ m_autoScrollDelayTime = 0;
+ m_autoScrollIsReversed = false;
+ m_lastRenderTime = 0;
+}
+
+CGUIBaseContainer::CGUIBaseContainer(const CGUIBaseContainer& other)
+ : IGUIContainer(other),
+ m_renderOffset(other.m_renderOffset),
+ m_analogScrollCount(other.m_analogScrollCount),
+ m_lastHoldTime(other.m_lastHoldTime),
+ m_orientation(other.m_orientation),
+ m_itemsPerPage(other.m_itemsPerPage),
+ m_pageControl(other.m_pageControl),
+ m_layoutCondition(other.m_layoutCondition),
+ m_focusedLayoutCondition(other.m_focusedLayoutCondition),
+ m_scroller(other.m_scroller),
+ m_listProvider(other.m_listProvider ? other.m_listProvider->Clone() : nullptr),
+ m_wasReset(other.m_wasReset),
+ m_letterOffsets(other.m_letterOffsets),
+ m_autoScrollCondition(other.m_autoScrollCondition),
+ m_autoScrollMoveTime(other.m_autoScrollMoveTime),
+ m_autoScrollDelayTime(other.m_autoScrollDelayTime),
+ m_autoScrollIsReversed(other.m_autoScrollIsReversed),
+ m_lastRenderTime(other.m_lastRenderTime),
+ m_cursor(other.m_cursor),
+ m_offset(other.m_offset),
+ m_cacheItems(other.m_cacheItems),
+ m_scrollTimer(other.m_scrollTimer),
+ m_lastScrollStartTimer(other.m_lastScrollStartTimer),
+ m_pageChangeTimer(other.m_pageChangeTimer),
+ m_clickActions(other.m_clickActions),
+ m_focusActions(other.m_focusActions),
+ m_unfocusActions(other.m_unfocusActions),
+ m_matchTimer(other.m_matchTimer),
+ m_match(other.m_match),
+ m_scrollItemsPerFrame(other.m_scrollItemsPerFrame),
+ m_gestureActive(other.m_gestureActive),
+ m_waitForScrollEnd(other.m_waitForScrollEnd),
+ m_lastScrollValue(other.m_lastScrollValue)
+{
+ // Initialize CGUIControl
+ m_bInvalidated = true;
+
+ for (const auto& item : other.m_items)
+ m_items.emplace_back(std::make_shared<CGUIListItem>(*item));
+
+ for (const auto& layout : other.m_layouts)
+ m_layouts.emplace_back(layout, this);
+
+ for (const auto& focusedLayout : other.m_focusedLayouts)
+ m_focusedLayouts.emplace_back(focusedLayout, this);
+}
+
+CGUIBaseContainer::~CGUIBaseContainer(void)
+{
+ // release the container from items
+ for (const auto& item : m_items)
+ item->FreeMemory();
+}
+
+void CGUIBaseContainer::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ CGUIControl::DoProcess(currentTime, dirtyregions);
+
+ if (m_pageChangeTimer.IsRunning() && m_pageChangeTimer.GetElapsedMilliseconds() > 200)
+ m_pageChangeTimer.Stop();
+ m_wasReset = false;
+
+ // if not visible, we reset the autoscroll timer
+ if (!IsVisible() && m_autoScrollMoveTime)
+ {
+ ResetAutoScrolling();
+ }
+}
+
+void CGUIBaseContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ // update our auto-scrolling as necessary
+ UpdateAutoScrolling(currentTime);
+
+ if (!m_waitForScrollEnd && !m_gestureActive)
+ ValidateOffset();
+
+ if (m_bInvalidated)
+ UpdateLayout();
+
+ if (!m_layout || !m_focusedLayout) return;
+
+ UpdateScrollOffset(currentTime);
+
+ if (m_scroller.IsScrolling())
+ MarkDirtyRegion();
+
+ int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ // Free memory not used on screen
+ if ((int)m_items.size() > m_itemsPerPage + cacheBefore + cacheAfter)
+ FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + m_itemsPerPage + 1 + cacheAfter, 0));
+
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+
+ // we offset our draw position to take into account scrolling and whether or not our focused
+ // item is offscreen "above" the list.
+ float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ if (GetOffset() + GetCursor() < offset)
+ drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
+ pos += drawOffset;
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ int current = offset - cacheBefore;
+ while (pos < end && m_items.size())
+ {
+ int itemNo = CorrectOffset(current, 0);
+ if (itemNo >= (int)m_items.size())
+ break;
+ bool focused = (current == GetOffset() + GetCursor());
+ if (itemNo >= 0)
+ {
+ CGUIListItemPtr item = m_items[itemNo];
+ item->SetCurrentItem(itemNo + 1);
+
+ // render our item
+ if (m_orientation == VERTICAL)
+ ProcessItem(origin.x, pos, item, focused, currentTime, dirtyregions);
+ else
+ ProcessItem(pos, origin.y, item, focused, currentTime, dirtyregions);
+ }
+ // increment our position
+ pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
+ current++;
+ }
+
+ // when we are scrolling up, offset will become lower (integer division, see offset calc)
+ // to have same behaviour when scrolling down, we need to set page control to offset+1
+ UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
+
+ m_lastRenderTime = currentTime;
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIBaseContainer::ProcessItem(float posX, float posY, CGUIListItemPtr& item, bool focused, unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (!m_focusedLayout || !m_layout) return;
+
+ // set the origin
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
+
+ if (m_bInvalidated)
+ item->SetInvalid();
+ if (focused)
+ {
+ if (!item->GetFocusedLayout())
+ {
+ item->SetFocusedLayout(std::make_unique<CGUIListItemLayout>(*m_focusedLayout, this));
+ }
+ if (item->GetFocusedLayout())
+ {
+ if (item != m_lastItem || !HasFocus())
+ {
+ item->GetFocusedLayout()->SetFocusedItem(0);
+ }
+ if (item != m_lastItem && HasFocus())
+ {
+ item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
+ unsigned int subItem = 1;
+ if (m_lastItem && m_lastItem->GetFocusedLayout())
+ subItem = m_lastItem->GetFocusedLayout()->GetFocusedItem();
+ item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
+ }
+ item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ }
+ m_lastItem = item;
+ }
+ else
+ {
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set
+ if (!item->GetLayout())
+ {
+ CGUIListItemLayoutPtr layout = std::make_unique<CGUIListItemLayout>(*m_layout, this);
+ item->SetLayout(std::move(layout));
+ }
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ if (item->GetLayout())
+ item->GetLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+}
+
+void CGUIBaseContainer::Render()
+{
+ if (!m_layout || !m_focusedLayout) return;
+
+ int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
+ {
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+
+ // we offset our draw position to take into account scrolling and whether or not our focused
+ // item is offscreen "above" the list.
+ float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ if (GetOffset() + GetCursor() < offset)
+ drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
+ pos += drawOffset;
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ float focusedPos = 0;
+ CGUIListItemPtr focusedItem;
+ int current = offset - cacheBefore;
+ while (pos < end && m_items.size())
+ {
+ int itemNo = CorrectOffset(current, 0);
+ if (itemNo >= (int)m_items.size())
+ break;
+ bool focused = (current == GetOffset() + GetCursor());
+ if (itemNo >= 0)
+ {
+ CGUIListItemPtr item = m_items[itemNo];
+ // render our item
+ if (focused)
+ {
+ focusedPos = pos;
+ focusedItem = item;
+ }
+ else
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x, pos, item.get(), false);
+ else
+ RenderItem(pos, origin.y, item.get(), false);
+ }
+ }
+ // increment our position
+ pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
+ current++;
+ }
+ // render focused item last so it can overlap other items
+ if (focusedItem)
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x, focusedPos, focusedItem.get(), true);
+ else
+ RenderItem(focusedPos, origin.y, focusedItem.get(), true);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+
+ CGUIControl::Render();
+}
+
+
+void CGUIBaseContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
+{
+ if (!m_focusedLayout || !m_layout) return;
+
+ // set the origin
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX, posY);
+
+ if (focused)
+ {
+ if (item->GetFocusedLayout())
+ item->GetFocusedLayout()->Render(item, m_parentID);
+ }
+ else
+ {
+ if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
+ item->GetFocusedLayout()->Render(item, m_parentID);
+ else if (item->GetLayout())
+ item->GetLayout()->Render(item, m_parentID);
+ }
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
+}
+
+bool CGUIBaseContainer::OnAction(const CAction &action)
+{
+ if (action.GetID() == KEY_UNICODE)
+ {
+ std::string letter;
+ g_charsetConverter.wToUTF8({action.GetUnicode()}, letter);
+ OnJumpLetter(letter);
+ return true;
+ }
+ // stop the timer on any other action
+ m_matchTimer.Stop();
+
+ switch (action.GetID())
+ {
+ case ACTION_MOVE_LEFT:
+ case ACTION_MOVE_RIGHT:
+ case ACTION_MOVE_DOWN:
+ case ACTION_MOVE_UP:
+ case ACTION_NAV_BACK:
+ case ACTION_PREVIOUS_MENU:
+ {
+ if (!HasFocus()) return false;
+
+ if (action.GetHoldTime() > HOLD_TIME_START &&
+ ((m_orientation == VERTICAL && (action.GetID() == ACTION_MOVE_UP || action.GetID() == ACTION_MOVE_DOWN)) ||
+ (m_orientation == HORIZONTAL && (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_RIGHT))))
+ { // action is held down - repeat a number of times
+ float speed = std::min(1.0f, (float)(action.GetHoldTime() - HOLD_TIME_START) / (HOLD_TIME_END - HOLD_TIME_START));
+ unsigned int frameDuration = std::min(CTimeUtils::GetFrameTime() - m_lastHoldTime, 50u); // max 20fps
+
+ // maximal scroll rate is at least 30 items per second, and at most (item_rows/7) items per second
+ // i.e. timed to take 7 seconds to traverse the list at full speed.
+ // minimal scroll rate is at least 10 items per second
+ float maxSpeed = std::max(frameDuration * 0.001f * 30, frameDuration * 0.001f * GetRows() / 7);
+ float minSpeed = frameDuration * 0.001f * 10;
+ m_scrollItemsPerFrame += std::max(minSpeed, speed*maxSpeed); // accelerate to max speed
+ m_lastHoldTime = CTimeUtils::GetFrameTime();
+
+ if(m_scrollItemsPerFrame < 1.0f)//not enough hold time accumulated for one step
+ return true;
+
+ while (m_scrollItemsPerFrame >= 1)
+ {
+ if (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_UP)
+ MoveUp(false);
+ else
+ MoveDown(false);
+ m_scrollItemsPerFrame--;
+ }
+ return true;
+ }
+ else
+ {
+ //if HOLD_TIME_START is reached we need
+ //a sane initial value for calculating m_scrollItemsPerPage
+ m_lastHoldTime = CTimeUtils::GetFrameTime();
+ m_scrollItemsPerFrame = 0.0f;
+ return CGUIControl::OnAction(action);
+ }
+ }
+ case ACTION_CONTEXT_MENU:
+ if (OnContextMenu())
+ return true;
+ break;
+ case ACTION_SHOW_INFO:
+ if (m_listProvider)
+ {
+ const int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ if (m_listProvider->OnInfo(m_items[selected]))
+ return true;
+ }
+ }
+ if (OnInfo())
+ return true;
+ else if (action.GetID())
+ return OnClick(action.GetID());
+
+ return false;
+
+ case ACTION_PLAYER_PLAY:
+ if (m_listProvider)
+ {
+ const int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ if (m_listProvider->OnPlay(m_items[selected]))
+ return true;
+ }
+ }
+ break;
+
+ case ACTION_FIRST_PAGE:
+ SelectItem(0);
+ return true;
+
+ case ACTION_LAST_PAGE:
+ if (m_items.size())
+ SelectItem(m_items.size() - 1);
+ return true;
+
+ case ACTION_NEXT_LETTER:
+ OnNextLetter();
+ return true;
+ case ACTION_PREV_LETTER:
+ OnPrevLetter();
+ return true;
+ case ACTION_JUMP_SMS2:
+ case ACTION_JUMP_SMS3:
+ case ACTION_JUMP_SMS4:
+ case ACTION_JUMP_SMS5:
+ case ACTION_JUMP_SMS6:
+ case ACTION_JUMP_SMS7:
+ case ACTION_JUMP_SMS8:
+ case ACTION_JUMP_SMS9:
+ OnJumpSMS(action.GetID() - ACTION_JUMP_SMS2 + 2);
+ return true;
+
+ default:
+ break;
+ }
+ return action.GetID() && OnClick(action.GetID());
+}
+
+bool CGUIBaseContainer::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID() )
+ {
+ if (!m_listProvider)
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
+ { // bind our items
+ Reset();
+ CFileItemList *items = static_cast<CFileItemList*>(message.GetPointer());
+ for (int i = 0; i < items->Size(); i++)
+ m_items.push_back(items->Get(i));
+ UpdateLayout(true); // true to refresh all items
+ UpdateScrollByLetter();
+ SelectItem(message.GetParam1());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_LABEL_RESET)
+ {
+ Reset();
+ SetPageControlRange();
+ return true;
+ }
+ }
+ if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
+ {
+ SelectItem(message.GetParam1());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_SETFOCUS)
+ {
+ if (message.GetParam1()) // subfocus item is specified, so set the offset appropriately
+ {
+ int offset = GetOffset();
+ if (message.GetParam2() && message.GetParam2() == 1)
+ offset = 0;
+ int item = std::min(offset + message.GetParam1() - 1, (int)m_items.size() - 1);
+ SelectItem(item);
+ }
+ }
+ else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
+ {
+ message.SetParam1(GetSelectedItem());
+ return true;
+ }
+ else if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
+ {
+ if (message.GetSenderId() == m_pageControl && IsVisible())
+ { // update our page if we're visible - not much point otherwise
+ if (message.GetParam1() != GetOffset())
+ m_pageChangeTimer.StartZero();
+ ScrollToOffset(message.GetParam1());
+ return true;
+ }
+ }
+ else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
+ { // update our list contents
+ for (unsigned int i = 0; i < m_items.size(); ++i)
+ m_items[i]->SetInvalid();
+ }
+ else if (message.GetMessage() == GUI_MSG_REFRESH_THUMBS)
+ {
+ if (m_listProvider)
+ m_listProvider->FreeResources(true);
+ }
+ else if (message.GetMessage() == GUI_MSG_MOVE_OFFSET)
+ {
+ int count = message.GetParam1();
+ while (count < 0)
+ {
+ MoveUp(true);
+ count++;
+ }
+ while (count > 0)
+ {
+ MoveDown(true);
+ count--;
+ }
+ return true;
+ }
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+void CGUIBaseContainer::OnUp()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_UP);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveUp(wrapAround))
+ return;
+ // with horizontal lists it doesn't make much sense to have multiselect labels
+ CGUIControl::OnUp();
+}
+
+void CGUIBaseContainer::OnDown()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_DOWN);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveDown(wrapAround))
+ return;
+ // with horizontal lists it doesn't make much sense to have multiselect labels
+ CGUIControl::OnDown();
+}
+
+void CGUIBaseContainer::OnLeft()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_LEFT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
+ return;
+ else if (m_orientation == VERTICAL)
+ {
+ CGUIListItemLayout *focusedLayout = GetFocusedLayout();
+ if (focusedLayout && focusedLayout->MoveLeft())
+ return;
+ }
+ CGUIControl::OnLeft();
+}
+
+void CGUIBaseContainer::OnRight()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
+ return;
+ else if (m_orientation == VERTICAL)
+ {
+ CGUIListItemLayout *focusedLayout = GetFocusedLayout();
+ if (focusedLayout && focusedLayout->MoveRight())
+ return;
+ }
+ CGUIControl::OnRight();
+}
+
+void CGUIBaseContainer::OnNextLetter()
+{
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
+ {
+ if (m_letterOffsets[i].first > offset)
+ {
+ SelectItem(m_letterOffsets[i].first);
+ return;
+ }
+ }
+}
+
+void CGUIBaseContainer::OnPrevLetter()
+{
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ if (!m_letterOffsets.size())
+ return;
+ for (int i = (int)m_letterOffsets.size() - 1; i >= 0; i--)
+ {
+ if (m_letterOffsets[i].first < offset)
+ {
+ SelectItem(m_letterOffsets[i].first);
+ return;
+ }
+ }
+}
+
+void CGUIBaseContainer::OnJumpLetter(const std::string& letter, bool skip /*=false*/)
+{
+ if (m_matchTimer.GetElapsedMilliseconds() < letter_match_timeout)
+ m_match += letter;
+ else
+ m_match = letter;
+
+ m_matchTimer.StartZero();
+
+ // we can't jump through letters if we have none
+ if (0 == m_letterOffsets.size())
+ return;
+
+ // find the current letter we're focused on
+ unsigned int offset = CorrectOffset(GetOffset(), GetCursor());
+ unsigned int i = (offset + ((skip) ? 1 : 0)) % m_items.size();
+ do
+ {
+ CGUIListItemPtr item = m_items[i];
+ std::string label = item->GetLabel();
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING))
+ label = SortUtils::RemoveArticles(label);
+ if (0 == StringUtils::CompareNoCase(label, m_match, m_match.size()))
+ {
+ SelectItem(i);
+ return;
+ }
+ i = (i+1) % m_items.size();
+ } while (i != offset);
+
+ // no match found - repeat with a single letter
+ std::wstring wmatch;
+ g_charsetConverter.utf8ToW(m_match, wmatch);
+ if (wmatch.length() > 1)
+ {
+ m_match.clear();
+ OnJumpLetter(letter, true);
+ }
+}
+
+void CGUIBaseContainer::OnJumpSMS(int letter)
+{
+ static const char letterMap[8][6] = { "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
+
+ // only 2..9 supported
+ if (letter < 2 || letter > 9 || !m_letterOffsets.size())
+ return;
+
+ const std::string letters = letterMap[letter - 2];
+ // find where we currently are
+ int offset = CorrectOffset(GetOffset(), GetCursor());
+ unsigned int currentLetter = 0;
+ while (currentLetter + 1 < m_letterOffsets.size() && m_letterOffsets[currentLetter + 1].first <= offset)
+ currentLetter++;
+
+ // now switch to the next letter
+ std::string current = m_letterOffsets[currentLetter].second;
+ size_t startPos = (letters.find(current) + 1) % letters.size();
+ // now jump to letters[startPos], or another one in the same range if possible
+ size_t pos = startPos;
+ while (true)
+ {
+ // check if we can jump to this letter
+ for (size_t i = 0; i < m_letterOffsets.size(); i++)
+ {
+ if (m_letterOffsets[i].second == letters.substr(pos, 1))
+ {
+ SelectItem(m_letterOffsets[i].first);
+ return;
+ }
+ }
+ pos = (pos + 1) % letters.size();
+ if (pos == startPos)
+ return;
+ }
+}
+
+bool CGUIBaseContainer::MoveUp(bool wrapAround)
+{
+ return true;
+}
+
+bool CGUIBaseContainer::MoveDown(bool wrapAround)
+{
+ return true;
+}
+
+// scrolls the said amount
+void CGUIBaseContainer::Scroll(int amount)
+{
+ ResetAutoScrolling();
+ ScrollToOffset(GetOffset() + amount);
+}
+
+int CGUIBaseContainer::GetSelectedItem() const
+{
+ return CorrectOffset(GetOffset(), GetCursor());
+}
+
+CGUIListItemPtr CGUIBaseContainer::GetListItem(int offset, unsigned int flag) const
+{
+ if (!m_items.size() || !m_layout)
+ return CGUIListItemPtr();
+ int item = GetSelectedItem() + offset;
+ if (flag & INFOFLAG_LISTITEM_POSITION) // use offset from the first item displayed, taking into account scrolling
+ item = CorrectOffset((int)(m_scroller.GetValue() / m_layout->Size(m_orientation)), offset);
+
+ if (flag & INFOFLAG_LISTITEM_ABSOLUTE) // use offset from the first item
+ item = CorrectOffset(0, offset);
+
+ if (flag & INFOFLAG_LISTITEM_WRAP)
+ {
+ item %= ((int)m_items.size());
+ if (item < 0) item += m_items.size();
+ return m_items[item];
+ }
+ else
+ {
+ if (item >= 0 && item < (int)m_items.size())
+ return m_items[item];
+ }
+ return CGUIListItemPtr();
+}
+
+CGUIListItemLayout *CGUIBaseContainer::GetFocusedLayout() const
+{
+ CGUIListItemPtr item = GetListItem(0);
+ if (item.get()) return item->GetFocusedLayout();
+ return NULL;
+}
+
+bool CGUIBaseContainer::OnMouseOver(const CPoint &point)
+{
+ // select the item under the pointer
+ if (!m_waitForScrollEnd)
+ SelectItemFromPoint(point - CPoint(m_posX, m_posY));
+ return CGUIControl::OnMouseOver(point);
+}
+
+EVENT_RESULT CGUIBaseContainer::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_MOUSE_LEFT_CLICK ||
+ event.m_id == ACTION_MOUSE_DOUBLE_CLICK ||
+ event.m_id == ACTION_MOUSE_RIGHT_CLICK)
+ {
+ // Cancel touch
+ m_waitForScrollEnd = false;
+ int select = GetSelectedItem();
+ if (SelectItemFromPoint(point - CPoint(m_posX, m_posY)))
+ {
+ if (event.m_id != ACTION_MOUSE_RIGHT_CLICK || select == GetSelectedItem())
+ OnClick(event.m_id);
+ return EVENT_RESULT_HANDLED;
+ }
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
+ {
+ Scroll(-1);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
+ {
+ Scroll(1);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_NOTIFY)
+ {
+ m_waitForScrollEnd = true;
+ m_lastScrollValue = m_scroller.GetValue();
+ return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL : EVENT_RESULT_PAN_VERTICAL;
+ }
+ else if (event.m_id == ACTION_GESTURE_BEGIN)
+ { // grab exclusive access
+ m_gestureActive = true;
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
+ SendWindowMessage(msg);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_PAN)
+ { // do the drag and validate our offset (corrects for end of scroll)
+ m_scroller.SetValue(m_scroller.GetValue() - ((m_orientation == HORIZONTAL) ? event.m_offsetX : event.m_offsetY));
+ float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
+ int offset = MathUtils::round_int(static_cast<double>(m_scroller.GetValue() / size));
+ m_lastScrollStartTimer.Stop();
+ m_scrollTimer.Start();
+ const int absCursor = CorrectOffset(GetOffset(), GetCursor());
+ SetOffset(offset);
+ ValidateOffset();
+ // Notify Application if Inertial scrolling reaches lists end
+ if (m_waitForScrollEnd)
+ {
+ if (fabs(m_scroller.GetValue() - m_lastScrollValue) < 0.001f)
+ {
+ m_waitForScrollEnd = false;
+ return EVENT_RESULT_UNHANDLED;
+ }
+ else
+ m_lastScrollValue = m_scroller.GetValue();
+ }
+ else
+ {
+ CGUIBaseContainer::SetCursor(absCursor - CorrectOffset(GetOffset(), 0));
+ }
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
+ { // release exclusive access
+ CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
+ SendWindowMessage(msg);
+ m_scrollTimer.Stop();
+ // and compute the nearest offset from this and scroll there
+ float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
+ float offset = m_scroller.GetValue() / size;
+ int toOffset = MathUtils::round_int(static_cast<double>(offset));
+ if (toOffset < offset)
+ SetOffset(toOffset+1);
+ else
+ SetOffset(toOffset-1);
+ ScrollToOffset(toOffset);
+ ValidateOffset();
+ SetCursor(GetCursor());
+ SetFocus(true);
+ m_waitForScrollEnd = false;
+ m_gestureActive = false;
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+bool CGUIBaseContainer::OnClick(int actionID)
+{
+ int subItem = 0;
+ if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
+ {
+ if (m_listProvider)
+ { // "select" action
+ int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ if (m_clickActions.HasActionsMeetingCondition())
+ m_clickActions.ExecuteActions(0, GetParentID(), m_items[selected]);
+ else
+ m_listProvider->OnClick(m_items[selected]);
+ }
+ return true;
+ }
+ // grab the currently focused subitem (if applicable)
+ CGUIListItemLayout *focusedLayout = GetFocusedLayout();
+ if (focusedLayout)
+ subItem = focusedLayout->GetFocusedItem();
+ }
+ else if (actionID == ACTION_MOUSE_RIGHT_CLICK)
+ {
+ if (OnContextMenu())
+ return true;
+ }
+ // Don't know what to do, so send to our parent window.
+ CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
+ return SendWindowMessage(msg);
+}
+
+bool CGUIBaseContainer::OnContextMenu()
+{
+ if (m_listProvider)
+ {
+ int selected = GetSelectedItem();
+ if (selected >= 0 && selected < static_cast<int>(m_items.size()))
+ {
+ m_listProvider->OnContextMenu(m_items[selected]);
+ return true;
+ }
+ }
+ return false;
+}
+
+std::string CGUIBaseContainer::GetDescription() const
+{
+ std::string strLabel;
+ int item = GetSelectedItem();
+ if (item >= 0 && item < (int)m_items.size())
+ {
+ CGUIListItemPtr pItem = m_items[item];
+ if (pItem->m_bIsFolder)
+ strLabel = StringUtils::Format("[{}]", pItem->GetLabel());
+ else
+ strLabel = pItem->GetLabel();
+ }
+ return strLabel;
+}
+
+void CGUIBaseContainer::SetFocus(bool bOnOff)
+{
+ if (bOnOff != HasFocus())
+ {
+ SetInvalid();
+ m_lastItem.reset();
+ }
+ CGUIControl::SetFocus(bOnOff);
+}
+
+void CGUIBaseContainer::SaveStates(std::vector<CControlState> &states)
+{
+ if (!m_listProvider || !m_listProvider->AlwaysFocusDefaultItem())
+ states.emplace_back(GetID(), GetSelectedItem());
+}
+
+void CGUIBaseContainer::SetPageControl(int id)
+{
+ m_pageControl = id;
+}
+
+bool CGUIBaseContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
+{
+ minOffset = 0;
+ maxOffset = GetRows() - m_itemsPerPage;
+ return true;
+}
+
+void CGUIBaseContainer::ValidateOffset()
+{
+}
+
+void CGUIBaseContainer::AllocResources()
+{
+ CGUIControl::AllocResources();
+ CalculateLayout();
+ if (m_listProvider)
+ {
+ UpdateListProvider(true);
+ }
+}
+
+void CGUIBaseContainer::FreeResources(bool immediately)
+{
+ CGUIControl::FreeResources(immediately);
+ if (m_listProvider)
+ {
+ if (immediately)
+ {
+ Reset();
+ m_listProvider->Reset();
+ }
+ }
+ m_scroller.Stop();
+}
+
+void CGUIBaseContainer::UpdateLayout(bool updateAllItems)
+{
+ if (updateAllItems)
+ { // free memory of items
+ for (iItems it = m_items.begin(); it != m_items.end(); ++it)
+ (*it)->FreeMemory();
+ }
+ // and recalculate the layout
+ CalculateLayout();
+ SetPageControlRange();
+ MarkDirtyRegion();
+}
+
+void CGUIBaseContainer::SetPageControlRange()
+{
+ if (m_pageControl)
+ {
+ CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetRows());
+ SendWindowMessage(msg);
+ }
+}
+
+void CGUIBaseContainer::UpdatePageControl(int offset)
+{
+ if (m_pageControl)
+ { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
+ CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, offset);
+ SendWindowMessage(msg);
+ }
+}
+
+void CGUIBaseContainer::UpdateVisibility(const CGUIListItem *item)
+{
+ CGUIControl::UpdateVisibility(item);
+
+ if (!IsVisible() && !CGUIControl::CanFocus())
+ return; // no need to update the content if we're not visible and we can't focus
+
+ // update layouts in case of condition changed
+ if ((m_layout && m_layout->CheckCondition() != m_layoutCondition) ||
+ (m_focusedLayout && m_focusedLayout->CheckCondition() != m_focusedLayoutCondition))
+ {
+ if (m_layout)
+ m_layoutCondition = m_layout->CheckCondition();
+ if (m_focusedLayout)
+ m_focusedLayoutCondition = m_focusedLayout->CheckCondition();
+
+ int itemIndex = GetSelectedItem();
+ UpdateLayout(true); // true to refresh all items
+ SelectItem(itemIndex);
+ }
+
+ UpdateListProvider();
+}
+
+void CGUIBaseContainer::UpdateListProvider(bool forceRefresh /* = false */)
+{
+ if (m_listProvider)
+ {
+ if (m_listProvider->Update(forceRefresh))
+ {
+ // save the current item
+ int currentItem = GetSelectedItem();
+ CGUIListItem *current = (currentItem >= 0 && currentItem < (int)m_items.size()) ? m_items[currentItem].get() : NULL;
+ const std::string prevSelectedPath((current && current->IsFileItem()) ? static_cast<CFileItem *>(current)->GetPath() : "");
+
+ Reset();
+ m_listProvider->Fetch(m_items);
+ SetPageControlRange();
+ // update the newly selected item
+ bool found = false;
+
+ // first, try to re-identify selected item by comparing item pointers, though it is not guaranteed that item instances got not recreated on update.
+ for (int i = 0; i < (int)m_items.size(); i++)
+ {
+ if (m_items[i].get() == current)
+ {
+ found = true;
+ if (i != currentItem)
+ {
+ SelectItem(i);
+ break;
+ }
+ }
+ }
+ if (!found && !prevSelectedPath.empty())
+ {
+ // as fallback, try to re-identify selected item by comparing item paths.
+ for (int i = 0; i < static_cast<int>(m_items.size()); i++)
+ {
+ const CGUIListItemPtr c(m_items[i]);
+ if (c->IsFileItem())
+ {
+ const std::string &selectedPath = static_cast<CFileItem *>(c.get())->GetPath();
+ if (selectedPath == prevSelectedPath)
+ {
+ found = true;
+ if (i != currentItem)
+ {
+ SelectItem(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!found && currentItem >= (int)m_items.size())
+ SelectItem(m_items.size()-1);
+ SetInvalid();
+ }
+ // always update the scroll by letter, as the list provider may have altered labels
+ // while not actually changing the list items.
+ UpdateScrollByLetter();
+ }
+}
+
+void CGUIBaseContainer::CalculateLayout()
+{
+ CGUIListItemLayout *oldFocusedLayout = m_focusedLayout;
+ CGUIListItemLayout *oldLayout = m_layout;
+ GetCurrentLayouts();
+
+ // calculate the number of items to display
+ if (!m_focusedLayout || !m_layout)
+ return;
+
+ if (oldLayout == m_layout && oldFocusedLayout == m_focusedLayout)
+ return; // nothing has changed, so don't update stuff
+
+ m_itemsPerPage = std::max((int)((Size() - m_focusedLayout->Size(m_orientation)) / m_layout->Size(m_orientation)) + 1, 1);
+
+ // ensure that the scroll offset is a multiple of our size
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+}
+
+void CGUIBaseContainer::UpdateScrollByLetter()
+{
+ m_letterOffsets.clear();
+
+ // for scrolling by letter we have an offset table into our vector.
+ std::string currentMatch;
+ for (unsigned int i = 0; i < m_items.size(); i++)
+ {
+ CGUIListItemPtr item = m_items[i];
+ // The letter offset jumping is only for ASCII characters at present, and
+ // our checks are all done in uppercase
+ std::string nextLetter;
+ std::wstring character = item->GetSortLabel().substr(0, 1);
+ StringUtils::ToUpper(character);
+ g_charsetConverter.wToUTF8(character, nextLetter);
+ if (currentMatch != nextLetter)
+ {
+ currentMatch = nextLetter;
+ m_letterOffsets.emplace_back(static_cast<int>(i), currentMatch);
+ }
+ }
+}
+
+unsigned int CGUIBaseContainer::GetRows() const
+{
+ return m_items.size();
+}
+
+inline float CGUIBaseContainer::Size() const
+{
+ return (m_orientation == HORIZONTAL) ? m_width : m_height;
+}
+
+int CGUIBaseContainer::ScrollCorrectionRange() const
+{
+ int range = m_itemsPerPage / 4;
+ if (range <= 0) range = 1;
+ return range;
+}
+
+void CGUIBaseContainer::ScrollToOffset(int offset)
+{
+ int minOffset, maxOffset;
+ if(GetOffsetRange(minOffset, maxOffset))
+ offset = std::max(minOffset, std::min(offset, maxOffset));
+ float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
+ int range = ScrollCorrectionRange();
+ if (offset * size < m_scroller.GetValue() && m_scroller.GetValue() - offset * size > size * range)
+ { // scrolling up, and we're jumping more than 0.5 of a screen
+ m_scroller.SetValue((offset + range) * size);
+ }
+ if (offset * size > m_scroller.GetValue() && offset * size - m_scroller.GetValue() > size * range)
+ { // scrolling down, and we're jumping more than 0.5 of a screen
+ m_scroller.SetValue((offset - range) * size);
+ }
+ m_scroller.ScrollTo(offset * size);
+ m_lastScrollStartTimer.StartZero();
+ if (!m_wasReset)
+ {
+ SetContainerMoving(offset - GetOffset());
+ if (m_scroller.IsScrolling())
+ m_scrollTimer.Start();
+ else
+ m_scrollTimer.Stop();
+ }
+ else
+ {
+ m_scrollTimer.Stop();
+ m_scroller.Update(~0U);
+ }
+ SetOffset(offset);
+}
+
+void CGUIBaseContainer::SetAutoScrolling(const TiXmlNode *node)
+{
+ if (!node) return;
+ const TiXmlElement *scroll = node->FirstChildElement("autoscroll");
+ if (scroll)
+ {
+ scroll->Attribute("time", &m_autoScrollMoveTime);
+ if (scroll->Attribute("reverse"))
+ m_autoScrollIsReversed = true;
+ if (scroll->FirstChild())
+ m_autoScrollCondition = CServiceBroker::GetGUI()->GetInfoManager().Register(scroll->FirstChild()->ValueStr(), GetParentID());
+ }
+}
+
+void CGUIBaseContainer::ResetAutoScrolling()
+{
+ m_autoScrollDelayTime = 0;
+}
+
+void CGUIBaseContainer::UpdateAutoScrolling(unsigned int currentTime)
+{
+ if (m_autoScrollCondition && m_autoScrollCondition->Get(INFO::DEFAULT_CONTEXT))
+ {
+ if (m_lastRenderTime)
+ m_autoScrollDelayTime += currentTime - m_lastRenderTime;
+ if (m_autoScrollDelayTime > (unsigned int)m_autoScrollMoveTime && !m_scroller.IsScrolling())
+ { // delay is finished - start moving
+ m_autoScrollDelayTime = 0;
+ // Move up or down whether reversed moving is true or false
+ m_autoScrollIsReversed ? MoveUp(true) : MoveDown(true);
+ }
+ }
+ else
+ ResetAutoScrolling();
+}
+
+void CGUIBaseContainer::SetContainerMoving(int direction)
+{
+ if (direction)
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetContainerMoving(GetID(), direction > 0, m_scroller.IsScrolling());
+}
+
+void CGUIBaseContainer::UpdateScrollOffset(unsigned int currentTime)
+{
+ if (m_scroller.Update(currentTime))
+ MarkDirtyRegion();
+ else if (m_lastScrollStartTimer.IsRunning() && m_lastScrollStartTimer.GetElapsedMilliseconds() >= SCROLLING_GAP)
+ {
+ m_scrollTimer.Stop();
+ m_lastScrollStartTimer.Stop();
+ SetCursor(GetCursor());
+ }
+}
+
+int CGUIBaseContainer::CorrectOffset(int offset, int cursor) const
+{
+ return offset + cursor;
+}
+
+void CGUIBaseContainer::Reset()
+{
+ m_wasReset = true;
+ m_items.clear();
+ m_lastItem.reset();
+ ResetAutoScrolling();
+}
+
+void CGUIBaseContainer::LoadLayout(TiXmlElement *layout)
+{
+ TiXmlElement *itemElement = layout->FirstChildElement("itemlayout");
+ while (itemElement)
+ { // we have a new item layout
+ m_layouts.emplace_back();
+ m_layouts.back().LoadLayout(itemElement, GetParentID(), false, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("itemlayout");
+ m_layouts.back().SetParentControl(this);
+ }
+ itemElement = layout->FirstChildElement("focusedlayout");
+ while (itemElement)
+ { // we have a new item layout
+ m_focusedLayouts.emplace_back();
+ m_focusedLayouts.back().LoadLayout(itemElement, GetParentID(), true, m_width, m_height);
+ itemElement = itemElement->NextSiblingElement("focusedlayout");
+ m_focusedLayouts.back().SetParentControl(this);
+ }
+}
+
+void CGUIBaseContainer::LoadListProvider(TiXmlElement *content, int defaultItem, bool defaultAlways)
+{
+ m_listProvider = IListProvider::Create(content, GetParentID());
+ if (m_listProvider)
+ m_listProvider->SetDefaultItem(defaultItem, defaultAlways);
+}
+
+void CGUIBaseContainer::SetListProvider(std::unique_ptr<IListProvider> provider)
+{
+ m_listProvider = std::move(provider);
+ UpdateListProvider(true);
+}
+
+void CGUIBaseContainer::SetRenderOffset(const CPoint &offset)
+{
+ m_renderOffset = offset;
+}
+
+void CGUIBaseContainer::FreeMemory(int keepStart, int keepEnd)
+{
+ if (keepStart < keepEnd)
+ { // remove before keepStart and after keepEnd
+ for (int i = 0; i < keepStart && i < (int)m_items.size(); ++i)
+ m_items[i]->FreeMemory();
+ for (int i = std::max(keepEnd + 1, 0); i < (int)m_items.size(); ++i)
+ m_items[i]->FreeMemory();
+ }
+ else
+ { // wrapping
+ for (int i = std::max(keepEnd + 1, 0); i < keepStart && i < (int)m_items.size(); ++i)
+ m_items[i]->FreeMemory();
+ }
+}
+
+bool CGUIBaseContainer::InsideLayout(const CGUIListItemLayout *layout, const CPoint &point) const
+{
+ if (!layout) return false;
+ if ((m_orientation == VERTICAL && (layout->Size(HORIZONTAL) > 1) && point.x > layout->Size(HORIZONTAL)) ||
+ (m_orientation == HORIZONTAL && (layout->Size(VERTICAL) > 1)&& point.y > layout->Size(VERTICAL)))
+ return false;
+ return true;
+}
+
+#ifdef _DEBUG
+void CGUIBaseContainer::DumpTextureUse()
+{
+ CLog::Log(LOGDEBUG, "{} for container {}", __FUNCTION__, GetID());
+ for (unsigned int i = 0; i < m_items.size(); ++i)
+ {
+ CGUIListItemPtr item = m_items[i];
+ if (item->GetFocusedLayout()) item->GetFocusedLayout()->DumpTextureUse();
+ if (item->GetLayout()) item->GetLayout()->DumpTextureUse();
+ }
+}
+#endif
+
+bool CGUIBaseContainer::GetCondition(int condition, int data) const
+{
+ switch (condition)
+ {
+ case CONTAINER_ROW:
+ return (m_orientation == VERTICAL) ? (GetCursor() == data) : true;
+ case CONTAINER_COLUMN:
+ return (m_orientation == HORIZONTAL) ? (GetCursor() == data) : true;
+ case CONTAINER_POSITION:
+ return (GetCursor() == data);
+ case CONTAINER_HAS_NEXT:
+ return (HasNextPage());
+ case CONTAINER_HAS_PREVIOUS:
+ return (HasPreviousPage());
+ case CONTAINER_HAS_PARENT_ITEM:
+ return (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder());
+ case CONTAINER_SUBITEM:
+ {
+ CGUIListItemLayout *layout = GetFocusedLayout();
+ return layout ? (layout->GetFocusedItem() == (unsigned int)data) : false;
+ }
+ case CONTAINER_SCROLLING:
+ return ((m_scrollTimer.IsRunning() && m_scrollTimer.GetElapsedMilliseconds() > std::max(m_scroller.GetDuration(), SCROLLING_THRESHOLD)) || m_pageChangeTimer.IsRunning());
+ case CONTAINER_ISUPDATING:
+ return (m_listProvider) ? m_listProvider->IsUpdating() : false;
+ default:
+ return false;
+ }
+}
+
+void CGUIBaseContainer::GetCurrentLayouts()
+{
+ m_layout = NULL;
+ for (auto &layout : m_layouts)
+ {
+ if (layout.CheckCondition())
+ {
+ m_layout = &layout;
+ break;
+ }
+ }
+ if (!m_layout && !m_layouts.empty())
+ m_layout = &m_layouts.front(); // failsafe
+
+ m_focusedLayout = NULL;
+ for (auto &layout : m_focusedLayouts)
+ {
+ if (layout.CheckCondition())
+ {
+ m_focusedLayout = &layout;
+ break;
+ }
+ }
+ if (!m_focusedLayout && !m_focusedLayouts.empty())
+ m_focusedLayout = &m_focusedLayouts.front(); // failsafe
+}
+
+bool CGUIBaseContainer::HasNextPage() const
+{
+ return false;
+}
+
+bool CGUIBaseContainer::HasPreviousPage() const
+{
+ return false;
+}
+
+std::string CGUIBaseContainer::GetLabel(int info) const
+{
+ std::string label;
+ switch (info)
+ {
+ case CONTAINER_NUM_PAGES:
+ label = std::to_string((GetRows() + m_itemsPerPage - 1) / m_itemsPerPage);
+ break;
+ case CONTAINER_CURRENT_PAGE:
+ label = std::to_string(GetCurrentPage());
+ break;
+ case CONTAINER_POSITION:
+ label = std::to_string(GetCursor());
+ break;
+ case CONTAINER_CURRENT_ITEM:
+ {
+ if (m_items.size() && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
+ label = std::to_string(GetSelectedItem());
+ else
+ label = std::to_string(GetSelectedItem() + 1);
+ }
+ break;
+ case CONTAINER_NUM_ALL_ITEMS:
+ case CONTAINER_NUM_ITEMS:
+ {
+ unsigned int numItems = GetNumItems();
+ if (info == CONTAINER_NUM_ITEMS && numItems && m_items[0]->IsFileItem() && (std::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
+ label = std::to_string(numItems - 1);
+ else
+ label = std::to_string(numItems);
+ }
+ break;
+ case CONTAINER_NUM_NONFOLDER_ITEMS:
+ {
+ int numItems = 0;
+ for (const auto& item : m_items)
+ {
+ if (!item->m_bIsFolder)
+ numItems++;
+ }
+ label = std::to_string(numItems);
+ }
+ break;
+ default:
+ break;
+ }
+ return label;
+}
+
+int CGUIBaseContainer::GetCurrentPage() const
+{
+ if (GetOffset() + m_itemsPerPage >= (int)GetRows()) // last page
+ return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
+ return GetOffset() / m_itemsPerPage + 1;
+}
+
+void CGUIBaseContainer::GetCacheOffsets(int &cacheBefore, int &cacheAfter) const
+{
+ if (m_scroller.IsScrollingDown())
+ {
+ cacheBefore = 0;
+ cacheAfter = m_cacheItems;
+ }
+ else if (m_scroller.IsScrollingUp())
+ {
+ cacheBefore = m_cacheItems;
+ cacheAfter = 0;
+ }
+ else
+ {
+ cacheBefore = m_cacheItems / 2;
+ cacheAfter = m_cacheItems / 2;
+ }
+}
+
+void CGUIBaseContainer::SetCursor(int cursor)
+{
+ if (m_cursor != cursor)
+ MarkDirtyRegion();
+ m_cursor = cursor;
+}
+
+void CGUIBaseContainer::SetOffset(int offset)
+{
+ if (m_offset != offset)
+ MarkDirtyRegion();
+ m_offset = offset;
+}
+
+bool CGUIBaseContainer::CanFocus() const
+{
+ if (CGUIControl::CanFocus())
+ {
+ /*
+ We allow focus if we have items available or if we have a list provider
+ that's in the process of updating.
+ */
+ return !m_items.empty() || (m_listProvider && m_listProvider->IsUpdating());
+ }
+ return false;
+}
+
+void CGUIBaseContainer::OnFocus()
+{
+ if (m_listProvider && m_listProvider->AlwaysFocusDefaultItem())
+ SelectItem(m_listProvider->GetDefaultItem());
+
+ if (m_focusActions.HasAnyActions())
+ m_focusActions.ExecuteActions(GetID(), GetParentID());
+
+ CGUIControl::OnFocus();
+}
+
+void CGUIBaseContainer::OnUnFocus()
+{
+ if (m_unfocusActions.HasAnyActions())
+ m_unfocusActions.ExecuteActions(GetID(), GetParentID());
+
+ CGUIControl::OnUnFocus();
+}