1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIListContainer.h"
10 
11 #include "GUIListItemLayout.h"
12 #include "GUIMessage.h"
13 #include "input/Key.h"
14 #include "utils/StringUtils.h"
15 
CGUIListContainer(int parentID,int controlID,float posX,float posY,float width,float height,ORIENTATION orientation,const CScroller & scroller,int preloadItems)16 CGUIListContainer::CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
17     : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
18 {
19   ControlType = GUICONTAINER_LIST;
20   m_type = VIEW_TYPE_LIST;
21 }
22 
23 CGUIListContainer::~CGUIListContainer(void) = default;
24 
OnAction(const CAction & action)25 bool CGUIListContainer::OnAction(const CAction &action)
26 {
27   switch (action.GetID())
28   {
29   case ACTION_PAGE_UP:
30     {
31       if (GetOffset() == 0)
32       { // already on the first page, so move to the first item
33         SetCursor(0);
34       }
35       else
36       { // scroll up to the previous page
37         Scroll( -m_itemsPerPage);
38       }
39       return true;
40     }
41     break;
42   case ACTION_PAGE_DOWN:
43     {
44       if (GetOffset() == (int)m_items.size() - m_itemsPerPage || (int)m_items.size() < m_itemsPerPage)
45       { // already at the last page, so move to the last item.
46         SetCursor(m_items.size() - GetOffset() - 1);
47       }
48       else
49       { // scroll down to the next page
50         Scroll(m_itemsPerPage);
51       }
52       return true;
53     }
54     break;
55     // smooth scrolling (for analog controls)
56   case ACTION_SCROLL_UP:
57     {
58       m_analogScrollCount += action.GetAmount() * action.GetAmount();
59       bool handled = false;
60       while (m_analogScrollCount > 0.4)
61       {
62         handled = true;
63         m_analogScrollCount -= 0.4f;
64         if (GetOffset() > 0 && GetCursor() <= m_itemsPerPage / 2)
65         {
66           Scroll(-1);
67         }
68         else if (GetCursor() > 0)
69         {
70           SetCursor(GetCursor() - 1);
71         }
72       }
73       return handled;
74     }
75     break;
76   case ACTION_SCROLL_DOWN:
77     {
78       m_analogScrollCount += action.GetAmount() * action.GetAmount();
79       bool handled = false;
80       while (m_analogScrollCount > 0.4)
81       {
82         handled = true;
83         m_analogScrollCount -= 0.4f;
84         if (GetOffset() + m_itemsPerPage < (int)m_items.size() && GetCursor() >= m_itemsPerPage / 2)
85         {
86           Scroll(1);
87         }
88         else if (GetCursor() < m_itemsPerPage - 1 && GetOffset() + GetCursor() < (int)m_items.size() - 1)
89         {
90           SetCursor(GetCursor() + 1);
91         }
92       }
93       return handled;
94     }
95     break;
96   }
97   return CGUIBaseContainer::OnAction(action);
98 }
99 
OnMessage(CGUIMessage & message)100 bool CGUIListContainer::OnMessage(CGUIMessage& message)
101 {
102   if (message.GetControlId() == GetID() )
103   {
104     if (message.GetMessage() == GUI_MSG_LABEL_RESET)
105     {
106       SetCursor(0);
107       SetOffset(0);
108       m_scroller.SetValue(0);
109     }
110   }
111   return CGUIBaseContainer::OnMessage(message);
112 }
113 
MoveUp(bool wrapAround)114 bool CGUIListContainer::MoveUp(bool wrapAround)
115 {
116   if (GetCursor() > 0)
117   {
118     SetCursor(GetCursor() - 1);
119   }
120   else if (GetCursor() == 0 && GetOffset())
121   {
122     ScrollToOffset(GetOffset() - 1);
123   }
124   else if (wrapAround)
125   {
126     if (!m_items.empty())
127     { // move 2 last item in list, and set our container moving up
128       int offset = m_items.size() - m_itemsPerPage;
129       if (offset < 0) offset = 0;
130       SetCursor(m_items.size() - offset - 1);
131       ScrollToOffset(offset);
132       SetContainerMoving(-1);
133     }
134   }
135   else
136     return false;
137   return true;
138 }
139 
MoveDown(bool wrapAround)140 bool CGUIListContainer::MoveDown(bool wrapAround)
141 {
142   if (GetOffset() + GetCursor() + 1 < (int)m_items.size())
143   {
144     if (GetCursor() + 1 < m_itemsPerPage)
145     {
146       SetCursor(GetCursor() + 1);
147     }
148     else
149     {
150       ScrollToOffset(GetOffset() + 1);
151     }
152   }
153   else if(wrapAround)
154   { // move first item in list, and set our container moving in the "down" direction
155     SetCursor(0);
156     ScrollToOffset(0);
157     SetContainerMoving(1);
158   }
159   else
160     return false;
161   return true;
162 }
163 
164 // scrolls the said amount
Scroll(int amount)165 void CGUIListContainer::Scroll(int amount)
166 {
167   // increase or decrease the offset
168   int offset = GetOffset() + amount;
169   if (offset > (int)m_items.size() - m_itemsPerPage)
170   {
171     offset = m_items.size() - m_itemsPerPage;
172   }
173   if (offset < 0) offset = 0;
174   ScrollToOffset(offset);
175 }
176 
ValidateOffset()177 void CGUIListContainer::ValidateOffset()
178 {
179   if (!m_layout) return;
180   // first thing is we check the range of our offset
181   // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
182   int minOffset, maxOffset;
183   GetOffsetRange(minOffset, maxOffset);
184   if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation)))
185   {
186     SetOffset(std::max(0, maxOffset));
187     m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
188   }
189   if (GetOffset() < 0 || (!m_scroller.IsScrolling() && m_scroller.GetValue() < 0))
190   {
191     SetOffset(0);
192     m_scroller.SetValue(0);
193   }
194 }
195 
SetCursor(int cursor)196 void CGUIListContainer::SetCursor(int cursor)
197 {
198   if (cursor > m_itemsPerPage - 1) cursor = m_itemsPerPage - 1;
199   if (cursor < 0) cursor = 0;
200   if (!m_wasReset)
201     SetContainerMoving(cursor - GetCursor());
202   CGUIBaseContainer::SetCursor(cursor);
203 }
204 
SelectItem(int item)205 void CGUIListContainer::SelectItem(int item)
206 {
207   // Check that our offset is valid
208   ValidateOffset();
209   // only select an item if it's in a valid range
210   if (item >= 0 && item < (int)m_items.size())
211   {
212     // Select the item requested
213     if (item >= GetOffset() && item < GetOffset() + m_itemsPerPage)
214     { // the item is on the current page, so don't change it.
215       SetCursor(item - GetOffset());
216     }
217     else if (item < GetOffset())
218     { // item is on a previous page - make it the first item on the page
219       SetCursor(0);
220       ScrollToOffset(item);
221     }
222     else // (item >= GetOffset()+m_itemsPerPage)
223     { // item is on a later page - make it the last item on the page
224       SetCursor(m_itemsPerPage - 1);
225       ScrollToOffset(item - GetCursor());
226     }
227   }
228 }
229 
GetCursorFromPoint(const CPoint & point,CPoint * itemPoint) const230 int CGUIListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
231 {
232   if (!m_focusedLayout || !m_layout)
233     return -1;
234 
235   int row = 0;
236   float pos = (m_orientation == VERTICAL) ? point.y : point.x;
237   while (row < m_itemsPerPage + 1)  // 1 more to ensure we get the (possible) half item at the end.
238   {
239     const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout;
240     if (pos < layout->Size(m_orientation) && row + GetOffset() < (int)m_items.size())
241     { // found correct "row" -> check horizontal
242       if (!InsideLayout(layout, point))
243         return -1;
244 
245       if (itemPoint)
246         *itemPoint = m_orientation == VERTICAL ? CPoint(point.x, pos) : CPoint(pos, point.y);
247       return row;
248     }
249     row++;
250     pos -= layout->Size(m_orientation);
251   }
252   return -1;
253 }
254 
SelectItemFromPoint(const CPoint & point)255 bool CGUIListContainer::SelectItemFromPoint(const CPoint &point)
256 {
257   CPoint itemPoint;
258   int row = GetCursorFromPoint(point, &itemPoint);
259   if (row < 0)
260     return false;
261 
262   SetCursor(row);
263   CGUIListItemLayout *focusedLayout = GetFocusedLayout();
264   if (focusedLayout)
265     focusedLayout->SelectItemFromPoint(itemPoint);
266   return true;
267 }
268 
269 //#ifdef GUILIB_PYTHON_COMPATIBILITY
CGUIListContainer(int parentID,int controlID,float posX,float posY,float width,float height,const CLabelInfo & labelInfo,const CLabelInfo & labelInfo2,const CTextureInfo & textureButton,const CTextureInfo & textureButtonFocus,float textureHeight,float itemWidth,float itemHeight,float spaceBetweenItems)270 CGUIListContainer::CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height,
271                                  const CLabelInfo& labelInfo, const CLabelInfo& labelInfo2,
272                                  const CTextureInfo& textureButton, const CTextureInfo& textureButtonFocus,
273                                  float textureHeight, float itemWidth, float itemHeight, float spaceBetweenItems)
274 : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, VERTICAL, 200, 0)
275 {
276   m_layouts.emplace_back();
277   m_layouts.back().CreateListControlLayouts(width, textureHeight + spaceBetweenItems, false, labelInfo, labelInfo2, textureButton, textureButtonFocus, textureHeight, itemWidth, itemHeight, "", "");
278   std::string condition = StringUtils::Format("control.hasfocus(%i)", controlID);
279   std::string condition2 = "!" + condition;
280   m_focusedLayouts.emplace_back();
281   m_focusedLayouts.back().CreateListControlLayouts(width, textureHeight + spaceBetweenItems, true, labelInfo, labelInfo2, textureButton, textureButtonFocus, textureHeight, itemWidth, itemHeight, condition2, condition);
282   m_height = floor(m_height / (textureHeight + spaceBetweenItems)) * (textureHeight + spaceBetweenItems);
283   ControlType = GUICONTAINER_LIST;
284 }
285 //#endif
286 
HasNextPage() const287 bool CGUIListContainer::HasNextPage() const
288 {
289   return (GetOffset() != (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage);
290 }
291 
HasPreviousPage() const292 bool CGUIListContainer::HasPreviousPage() const
293 {
294   return (GetOffset() > 0);
295 }
296