Merge pull request #4687 from ruuk/textboxgettext
[vuplus_xbmc] / xbmc / guilib / GUIPanelContainer.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIPanelContainer.h"
22 #include "GUIListItem.h"
23 #include "GUIInfoManager.h"
24 #include "Key.h"
25
26 using namespace std;
27
28 CGUIPanelContainer::CGUIPanelContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
29     : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
30 {
31   ControlType = GUICONTAINER_PANEL;
32   m_type = VIEW_TYPE_ICON;
33   m_itemsPerRow = 1;
34 }
35
36 CGUIPanelContainer::~CGUIPanelContainer(void)
37 {
38 }
39
40 void CGUIPanelContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
41 {
42   ValidateOffset();
43
44   if (m_bInvalidated)
45     UpdateLayout();
46
47   if (!m_layout || !m_focusedLayout) return;
48
49   UpdateScrollOffset(currentTime);
50
51   int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
52
53   int cacheBefore, cacheAfter;
54   GetCacheOffsets(cacheBefore, cacheAfter);
55
56   // Free memory not used on screen at the moment, do this first so there's more memory for the new items.
57   FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + cacheAfter + m_itemsPerPage + 1, 0));
58
59   CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
60   float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
61   float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
62   pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
63   end += cacheAfter * m_layout->Size(m_orientation);
64
65   int current = (offset - cacheBefore) * m_itemsPerRow;
66   int col = 0;
67   while (pos < end && m_items.size())
68   {
69     if (current >= (int)m_items.size())
70       break;
71     if (current >= 0)
72     {
73       CGUIListItemPtr item = m_items[current];
74       bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
75
76       if (m_orientation == VERTICAL)
77         ProcessItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item, focused, currentTime, dirtyregions);
78       else
79         ProcessItem(pos, origin.y + col * m_layout->Size(VERTICAL), item, focused, currentTime, dirtyregions);
80     }
81     // increment our position
82     if (col < m_itemsPerRow - 1)
83       col++;
84     else
85     {
86       pos += m_layout->Size(m_orientation);
87       col = 0;
88     }
89     current++;
90   }
91
92   // when we are scrolling up, offset will become lower (integer division, see offset calc)
93   // to have same behaviour when scrolling down, we need to set page control to offset+1
94   UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
95
96   CGUIControl::Process(currentTime, dirtyregions);
97 }
98
99
100 void CGUIPanelContainer::Render()
101 {
102   if (!m_layout || !m_focusedLayout) return;
103
104   int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
105
106   int cacheBefore, cacheAfter;
107   GetCacheOffsets(cacheBefore, cacheAfter);
108
109   // Free memory not used on screen at the moment, do this first so there's more memory for the new items.
110   FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + cacheAfter + m_itemsPerPage + 1, 0));
111
112   if (g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height))
113   {
114     CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
115     float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
116     float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
117     pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
118     end += cacheAfter * m_layout->Size(m_orientation);
119
120     float focusedPos = 0;
121     int focusedCol = 0;
122     CGUIListItemPtr focusedItem;
123     int current = (offset - cacheBefore) * m_itemsPerRow;
124     int col = 0;
125     while (pos < end && m_items.size())
126     {
127       if (current >= (int)m_items.size())
128         break;
129       if (current >= 0)
130       {
131         CGUIListItemPtr item = m_items[current];
132         bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
133         // render our item
134         if (focused)
135         {
136           focusedPos = pos;
137           focusedCol = col;
138           focusedItem = item;
139         }
140         else
141         {
142           if (m_orientation == VERTICAL)
143             RenderItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item.get(), false);
144           else
145             RenderItem(pos, origin.y + col * m_layout->Size(VERTICAL), item.get(), false);
146         }
147       }
148       // increment our position
149       if (col < m_itemsPerRow - 1)
150         col++;
151       else
152       {
153         pos += m_layout->Size(m_orientation);
154         col = 0;
155       }
156       current++;
157     }
158     // and render the focused item last (for overlapping purposes)
159     if (focusedItem)
160     {
161       if (m_orientation == VERTICAL)
162         RenderItem(origin.x + focusedCol * m_layout->Size(HORIZONTAL), focusedPos, focusedItem.get(), true);
163       else
164         RenderItem(focusedPos, origin.y + focusedCol * m_layout->Size(VERTICAL), focusedItem.get(), true);
165     }
166
167     g_graphicsContext.RestoreClipRegion();
168   }
169   CGUIControl::Render();
170 }
171
172 bool CGUIPanelContainer::OnAction(const CAction &action)
173 {
174   switch (action.GetID())
175   {
176   case ACTION_PAGE_UP:
177     {
178       if (GetOffset() == 0)
179       { // already on the first page, so move to the first item
180         SetCursor(0);
181       }
182       else
183       { // scroll up to the previous page
184         Scroll( -m_itemsPerPage);
185       }
186       return true;
187     }
188     break;
189   case ACTION_PAGE_DOWN:
190     {
191       if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow >= (int)m_items.size() || (int)m_items.size() < m_itemsPerPage)
192       { // already at the last page, so move to the last item.
193         SetCursor(m_items.size() - GetOffset() * m_itemsPerRow - 1);
194       }
195       else
196       { // scroll down to the next page
197         Scroll(m_itemsPerPage);
198       }
199       return true;
200     }
201     break;
202     // smooth scrolling (for analog controls)
203   case ACTION_SCROLL_UP:
204     {
205       m_analogScrollCount += action.GetAmount() * action.GetAmount();
206       bool handled = false;
207       while (m_analogScrollCount > AnalogScrollSpeed())
208       {
209         handled = true;
210         m_analogScrollCount -= AnalogScrollSpeed();
211         if (GetOffset() > 0)// && GetCursor() <= m_itemsPerPage * m_itemsPerRow / 2)
212         {
213           Scroll(-1);
214         }
215         else if (GetCursor() > 0)
216         {
217           SetCursor(GetCursor() - 1);
218         }
219       }
220       return handled;
221     }
222     break;
223   case ACTION_SCROLL_DOWN:
224     {
225       m_analogScrollCount += action.GetAmount() * action.GetAmount();
226       bool handled = false;
227       while (m_analogScrollCount > AnalogScrollSpeed())
228       {
229         handled = true;
230         m_analogScrollCount -= AnalogScrollSpeed();
231         if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow < (int)m_items.size())// && GetCursor() >= m_itemsPerPage * m_itemsPerRow / 2)
232         {
233           Scroll(1);
234         }
235         else if (GetCursor() < m_itemsPerPage * m_itemsPerRow - 1 && GetOffset() * m_itemsPerRow + GetCursor() < (int)m_items.size() - 1)
236         {
237           SetCursor(GetCursor() + 1);
238         }
239       }
240       return handled;
241     }
242     break;
243   }
244   return CGUIBaseContainer::OnAction(action);
245 }
246
247 bool CGUIPanelContainer::OnMessage(CGUIMessage& message)
248 {
249   if (message.GetControlId() == GetID() )
250   {
251     if (message.GetMessage() == GUI_MSG_LABEL_RESET)
252     {
253       SetCursor(0);
254       // fall through to base class
255     }
256   }
257   return CGUIBaseContainer::OnMessage(message);
258 }
259
260 void CGUIPanelContainer::OnLeft()
261 {
262   bool wrapAround = m_actionLeft.GetNavigation() == GetID() || !m_actionLeft.HasActionsMeetingCondition();
263   if (m_orientation == VERTICAL && MoveLeft(wrapAround))
264     return;
265   if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
266     return;
267   CGUIControl::OnLeft();
268 }
269
270 void CGUIPanelContainer::OnRight()
271 {
272   bool wrapAround = m_actionRight.GetNavigation() == GetID() || !m_actionRight.HasActionsMeetingCondition();
273   if (m_orientation == VERTICAL && MoveRight(wrapAround))
274     return;
275   if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
276     return;
277   return CGUIControl::OnRight();
278 }
279
280 void CGUIPanelContainer::OnUp()
281 {
282   bool wrapAround = m_actionUp.GetNavigation() == GetID() || !m_actionUp.HasActionsMeetingCondition();
283   if (m_orientation == VERTICAL && MoveUp(wrapAround))
284     return;
285   if (m_orientation == HORIZONTAL && MoveLeft(wrapAround))
286     return;
287   CGUIControl::OnUp();
288 }
289
290 void CGUIPanelContainer::OnDown()
291 {
292   bool wrapAround = m_actionDown.GetNavigation() == GetID() || !m_actionDown.HasActionsMeetingCondition();
293   if (m_orientation == VERTICAL && MoveDown(wrapAround))
294     return;
295   if (m_orientation == HORIZONTAL && MoveRight(wrapAround))
296     return;
297   return CGUIControl::OnDown();
298 }
299
300 bool CGUIPanelContainer::MoveDown(bool wrapAround)
301 {
302   if (GetCursor() + m_itemsPerRow < m_itemsPerPage * m_itemsPerRow && (GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
303   { // move to last item if necessary
304     if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
305       SetCursor((int)m_items.size() - 1 - GetOffset()*m_itemsPerRow);
306     else
307       SetCursor(GetCursor() + m_itemsPerRow);
308   }
309   else if ((GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
310   { // we scroll to the next row, and move to last item if necessary
311     if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
312       SetCursor((int)m_items.size() - 1 - (GetOffset() + 1)*m_itemsPerRow);
313     ScrollToOffset(GetOffset() + 1);
314   }
315   else if (wrapAround)
316   { // move first item in list
317     SetCursor(GetCursor() % m_itemsPerRow);
318     ScrollToOffset(0);
319     SetContainerMoving(1);
320   }
321   else
322     return false;
323   return true;
324 }
325
326 bool CGUIPanelContainer::MoveUp(bool wrapAround)
327 {
328   if (GetCursor() >= m_itemsPerRow)
329     SetCursor(GetCursor() - m_itemsPerRow);
330   else if (GetOffset() > 0)
331     ScrollToOffset(GetOffset() - 1);
332   else if (wrapAround)
333   { // move last item in list in this column
334     SetCursor((GetCursor() % m_itemsPerRow) + (m_itemsPerPage - 1) * m_itemsPerRow);
335     int offset = max((int)GetRows() - m_itemsPerPage, 0);
336     // should check here whether cursor is actually allowed here, and reduce accordingly
337     if (offset * m_itemsPerRow + GetCursor() >= (int)m_items.size())
338       SetCursor((int)m_items.size() - offset * m_itemsPerRow - 1);
339     ScrollToOffset(offset);
340     SetContainerMoving(-1);
341   }
342   else
343     return false;
344   return true;
345 }
346
347 bool CGUIPanelContainer::MoveLeft(bool wrapAround)
348 {
349   int col = GetCursor() % m_itemsPerRow;
350   if (col > 0)
351     SetCursor(GetCursor() - 1);
352   else if (wrapAround)
353   { // wrap around
354     SetCursor(GetCursor() + m_itemsPerRow - 1);
355     if (GetOffset() * m_itemsPerRow + GetCursor() >= (int)m_items.size())
356       SetCursor((int)m_items.size() - GetOffset() * m_itemsPerRow - 1);
357   }
358   else
359     return false;
360   return true;
361 }
362
363 bool CGUIPanelContainer::MoveRight(bool wrapAround)
364 {
365   int col = GetCursor() % m_itemsPerRow;
366   if (col + 1 < m_itemsPerRow && GetOffset() * m_itemsPerRow + GetCursor() + 1 < (int)m_items.size())
367     SetCursor(GetCursor() + 1);
368   else if (wrapAround) // move first item in row
369     SetCursor(GetCursor() - col);
370   else
371     return false;
372   return true;
373 }
374
375 // scrolls the said amount
376 void CGUIPanelContainer::Scroll(int amount)
377 {
378   // increase or decrease the offset
379   int offset = GetOffset() + amount;
380   if (offset > ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow)
381   {
382     offset = ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow;
383   }
384   if (offset < 0) offset = 0;
385   ScrollToOffset(offset);
386 }
387
388 void CGUIPanelContainer::ValidateOffset()
389 {
390   if (!m_layout) return;
391   // first thing is we check the range of our offset
392   // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
393   if (GetOffset() > (int)GetRows() - m_itemsPerPage || (!m_scroller.IsScrolling() && m_scroller.GetValue() > ((int)GetRows() - m_itemsPerPage) * m_layout->Size(m_orientation)))
394   {
395     SetOffset(std::max(0, (int)GetRows() - m_itemsPerPage));
396     m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
397   }
398   if (GetOffset() < 0 || (!m_scroller.IsScrolling() && m_scroller.GetValue() < 0))
399   {
400     SetOffset(0);
401     m_scroller.SetValue(0);
402   }
403 }
404
405 void CGUIPanelContainer::SetCursor(int cursor)
406 {
407   // +1 to ensure we're OK if we have a half item
408   if (cursor > (m_itemsPerPage + 1)*m_itemsPerRow - 1) cursor = (m_itemsPerPage + 1)*m_itemsPerRow - 1;
409   if (cursor < 0) cursor = 0;
410   if (!m_wasReset)
411     SetContainerMoving(cursor - GetCursor());
412   CGUIBaseContainer::SetCursor(cursor);
413 }
414
415 void CGUIPanelContainer::CalculateLayout()
416 {
417   GetCurrentLayouts();
418
419   if (!m_layout || !m_focusedLayout) return;
420   // calculate the number of items to display
421   if (m_orientation == HORIZONTAL)
422   {
423     m_itemsPerRow = (int)(m_height / m_layout->Size(VERTICAL));
424     m_itemsPerPage = (int)(m_width / m_layout->Size(HORIZONTAL));
425   }
426   else
427   {
428     m_itemsPerRow = (int)(m_width / m_layout->Size(HORIZONTAL));
429     m_itemsPerPage = (int)(m_height / m_layout->Size(VERTICAL));
430   }
431   if (m_itemsPerRow < 1) m_itemsPerRow = 1;
432   if (m_itemsPerPage < 1) m_itemsPerPage = 1;
433
434   // ensure that the scroll offset is a multiple of our size
435   m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
436 }
437
438 unsigned int CGUIPanelContainer::GetRows() const
439 {
440   assert(m_itemsPerRow > 0);
441   return (m_items.size() + m_itemsPerRow - 1) / m_itemsPerRow;
442 }
443
444 float CGUIPanelContainer::AnalogScrollSpeed() const
445 {
446   return 10.0f / m_itemsPerPage;
447 }
448
449 int CGUIPanelContainer::CorrectOffset(int offset, int cursor) const
450 {
451   return offset * m_itemsPerRow + cursor;
452 }
453
454 int CGUIPanelContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
455 {
456   if (!m_layout)
457     return -1;
458
459   float sizeX = m_orientation == VERTICAL ? m_layout->Size(HORIZONTAL) : m_layout->Size(VERTICAL);
460   float sizeY = m_orientation == VERTICAL ? m_layout->Size(VERTICAL) : m_layout->Size(HORIZONTAL);
461
462   float posY = m_orientation == VERTICAL ? point.y : point.x;
463   for (int y = 0; y < m_itemsPerPage + 1; y++) // +1 to ensure if we have a half item we can select it
464   {
465     float posX = m_orientation == VERTICAL ? point.x : point.y;
466     for (int x = 0; x < m_itemsPerRow; x++)
467     {
468       int item = x + y * m_itemsPerRow;
469       if (posX < sizeX && posY < sizeY && item + GetOffset() < (int)m_items.size())
470       { // found
471         return item;
472       }
473       posX -= sizeX;
474     }
475     posY -= sizeY;
476   }
477   return -1;
478 }
479
480 bool CGUIPanelContainer::SelectItemFromPoint(const CPoint &point)
481 {
482   int cursor = GetCursorFromPoint(point);
483   if (cursor < 0)
484     return false;
485   SetCursor(cursor);
486   return true;
487 }
488
489 bool CGUIPanelContainer::GetCondition(int condition, int data) const
490 { // probably only works vertically atm...
491   int row = GetCursor() / m_itemsPerRow;
492   int col = GetCursor() % m_itemsPerRow;
493   if (m_orientation == HORIZONTAL)
494     swap(row, col);
495   switch (condition)
496   {
497   case CONTAINER_ROW:
498     return (row == data);
499   case CONTAINER_COLUMN:
500     return (col == data);
501   default:
502     return CGUIBaseContainer::GetCondition(condition, data);
503   }
504 }
505
506 void CGUIPanelContainer::SelectItem(int item)
507 {
508   // Check that our offset is valid
509   ValidateOffset();
510   // only select an item if it's in a valid range
511   if (item >= 0 && item < (int)m_items.size())
512   {
513     // Select the item requested
514     if (item >= GetOffset() * m_itemsPerRow && item < (GetOffset() + m_itemsPerPage) * m_itemsPerRow)
515     { // the item is on the current page, so don't change it.
516       SetCursor(item - GetOffset() * m_itemsPerRow);
517     }
518     else if (item < GetOffset() * m_itemsPerRow)
519     { // item is on a previous page - make it the first item on the page
520       SetCursor(item % m_itemsPerRow);
521       ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
522     }
523     else // (item >= GetOffset()+m_itemsPerPage)
524     { // item is on a later page - make it the last row on the page
525       SetCursor(item % m_itemsPerRow + m_itemsPerRow * (m_itemsPerPage - 1));
526       ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
527     }
528   }
529 }
530
531 bool CGUIPanelContainer::HasPreviousPage() const
532 {
533   return (GetOffset() > 0);
534 }
535
536 bool CGUIPanelContainer::HasNextPage() const
537 {
538   return (GetOffset() != (int)GetRows() - m_itemsPerPage && (int)GetRows() > m_itemsPerPage);
539 }
540