[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / guilib / GUIBaseContainer.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 "GUIBaseContainer.h"
22 #include "GUIControlFactory.h"
23 #include "GUIWindowManager.h"
24 #include "utils/CharsetConverter.h"
25 #include "GUIInfoManager.h"
26 #include "utils/TimeUtils.h"
27 #include "utils/log.h"
28 #include "utils/SortUtils.h"
29 #include "utils/StringUtils.h"
30 #include "FileItem.h"
31 #include "Key.h"
32 #include "utils/MathUtils.h"
33 #include "utils/XBMCTinyXML.h"
34 #include "listproviders/IListProvider.h"
35
36 using namespace std;
37
38 #define HOLD_TIME_START 100
39 #define HOLD_TIME_END   3000
40 #define SCROLLING_GAP   200U
41 #define SCROLLING_THRESHOLD 300U
42
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)
46 {
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_listProvider = NULL;
61 }
62
63 CGUIBaseContainer::~CGUIBaseContainer(void)
64 {
65   delete m_listProvider;
66 }
67
68 void CGUIBaseContainer::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
69 {
70   CGUIControl::DoProcess(currentTime, dirtyregions);
71
72   if (m_pageChangeTimer.GetElapsedMilliseconds() > 200)
73     m_pageChangeTimer.Stop();
74   m_wasReset = false;
75 }
76
77 void CGUIBaseContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
78 {
79   ValidateOffset();
80
81   if (m_bInvalidated)
82     UpdateLayout();
83
84   if (!m_layout || !m_focusedLayout) return;
85
86   UpdateScrollOffset(currentTime);
87
88   int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
89
90   int cacheBefore, cacheAfter;
91   GetCacheOffsets(cacheBefore, cacheAfter);
92
93   // Free memory not used on screen
94   if ((int)m_items.size() > m_itemsPerPage + cacheBefore + cacheAfter)
95     FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + m_itemsPerPage + 1 + cacheAfter, 0));
96
97   CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
98   float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
99   float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
100
101   // we offset our draw position to take into account scrolling and whether or not our focused
102   // item is offscreen "above" the list.
103   float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
104   if (GetOffset() + GetCursor() < offset)
105     drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
106   pos += drawOffset;
107   end += cacheAfter * m_layout->Size(m_orientation);
108
109   int current = offset - cacheBefore;
110   while (pos < end && m_items.size())
111   {
112     int itemNo = CorrectOffset(current, 0);
113     if (itemNo >= (int)m_items.size())
114       break;
115     bool focused = (current == GetOffset() + GetCursor());
116     if (itemNo >= 0)
117     {
118       CGUIListItemPtr item = m_items[itemNo];
119       // render our item
120       if (m_orientation == VERTICAL)
121         ProcessItem(origin.x, pos, item, focused, currentTime, dirtyregions);
122       else
123         ProcessItem(pos, origin.y, item, focused, currentTime, dirtyregions);
124     }
125     // increment our position
126     pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
127     current++;
128   }
129
130   // when we are scrolling up, offset will become lower (integer division, see offset calc)
131   // to have same behaviour when scrolling down, we need to set page control to offset+1
132   UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
133
134   CGUIControl::Process(currentTime, dirtyregions);
135 }
136
137 void CGUIBaseContainer::ProcessItem(float posX, float posY, CGUIListItemPtr& item, bool focused, unsigned int currentTime, CDirtyRegionList &dirtyregions)
138 {
139   if (!m_focusedLayout || !m_layout) return;
140
141   // set the origin
142   g_graphicsContext.SetOrigin(posX, posY);
143
144   if (m_bInvalidated)
145     item->SetInvalid();
146   if (focused)
147   {
148     if (!item->GetFocusedLayout())
149     {
150       CGUIListItemLayout *layout = new CGUIListItemLayout(*m_focusedLayout);
151       item->SetFocusedLayout(layout);
152     }
153     if (item->GetFocusedLayout())
154     {
155       if (item != m_lastItem || !HasFocus())
156       {
157         item->GetFocusedLayout()->SetFocusedItem(0);
158       }
159       if (item != m_lastItem && HasFocus())
160       {
161         item->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS);
162         unsigned int subItem = 1;
163         if (m_lastItem && m_lastItem->GetFocusedLayout())
164           subItem = m_lastItem->GetFocusedLayout()->GetFocusedItem();
165         item->GetFocusedLayout()->SetFocusedItem(subItem ? subItem : 1);
166       }
167       item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
168     }
169     m_lastItem = item;
170   }
171   else
172   {
173     if (item->GetFocusedLayout())
174       item->GetFocusedLayout()->SetFocusedItem(0);  // focus is not set
175     if (!item->GetLayout())
176     {
177       CGUIListItemLayout *layout = new CGUIListItemLayout(*m_layout);
178       item->SetLayout(layout);
179     }
180     if (item->GetFocusedLayout())
181       item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
182     if (item->GetLayout())
183       item->GetLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
184   }
185
186   g_graphicsContext.RestoreOrigin();
187 }
188
189 void CGUIBaseContainer::Render()
190 {
191   if (!m_layout || !m_focusedLayout) return;
192
193   int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
194
195   int cacheBefore, cacheAfter;
196   GetCacheOffsets(cacheBefore, cacheAfter);
197
198   if (g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height))
199   {
200     CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
201     float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
202     float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
203
204     // we offset our draw position to take into account scrolling and whether or not our focused
205     // item is offscreen "above" the list.
206     float drawOffset = (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
207     if (GetOffset() + GetCursor() < offset)
208       drawOffset += m_focusedLayout->Size(m_orientation) - m_layout->Size(m_orientation);
209     pos += drawOffset;
210     end += cacheAfter * m_layout->Size(m_orientation);
211
212     float focusedPos = 0;
213     CGUIListItemPtr focusedItem;
214     int current = offset - cacheBefore;
215     while (pos < end && m_items.size())
216     {
217       int itemNo = CorrectOffset(current, 0);
218       if (itemNo >= (int)m_items.size())
219         break;
220       bool focused = (current == GetOffset() + GetCursor());
221       if (itemNo >= 0)
222       {
223         CGUIListItemPtr item = m_items[itemNo];
224         // render our item
225         if (focused)
226         {
227           focusedPos = pos;
228           focusedItem = item;
229         }
230         else
231         {
232           if (m_orientation == VERTICAL)
233             RenderItem(origin.x, pos, item.get(), false);
234           else
235             RenderItem(pos, origin.y, item.get(), false);
236         }
237       }
238       // increment our position
239       pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
240       current++;
241     }
242     // render focused item last so it can overlap other items
243     if (focusedItem)
244     {
245       if (m_orientation == VERTICAL)
246         RenderItem(origin.x, focusedPos, focusedItem.get(), true);
247       else
248         RenderItem(focusedPos, origin.y, focusedItem.get(), true);
249     }
250
251     g_graphicsContext.RestoreClipRegion();
252   }
253
254   CGUIControl::Render();
255 }
256
257
258 void CGUIBaseContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
259 {
260   if (!m_focusedLayout || !m_layout) return;
261
262   // set the origin
263   g_graphicsContext.SetOrigin(posX, posY);
264
265   if (focused)
266   {
267     if (item->GetFocusedLayout())
268       item->GetFocusedLayout()->Render(item, m_parentID);
269   }
270   else
271   {
272     if (item->GetFocusedLayout() && item->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS))
273       item->GetFocusedLayout()->Render(item, m_parentID);
274     else if (item->GetLayout())
275       item->GetLayout()->Render(item, m_parentID);
276   }
277   g_graphicsContext.RestoreOrigin();
278 }
279
280 bool CGUIBaseContainer::OnAction(const CAction &action)
281 {
282   if (action.GetID() >= KEY_ASCII)
283   {
284     OnJumpLetter((char)(action.GetID() & 0xff));
285     return true;
286   }
287   // stop the timer on any other action
288   m_matchTimer.Stop();
289
290   switch (action.GetID())
291   {
292   case ACTION_MOVE_LEFT:
293   case ACTION_MOVE_RIGHT:
294   case ACTION_MOVE_DOWN:
295   case ACTION_MOVE_UP:
296   case ACTION_NAV_BACK:
297   case ACTION_PREVIOUS_MENU:
298     {
299       if (!HasFocus()) return false;
300
301       if (action.GetHoldTime() > HOLD_TIME_START &&
302         ((m_orientation == VERTICAL && (action.GetID() == ACTION_MOVE_UP || action.GetID() == ACTION_MOVE_DOWN)) ||
303          (m_orientation == HORIZONTAL && (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_RIGHT))))
304       { // action is held down - repeat a number of times
305         float speed = std::min(1.0f, (float)(action.GetHoldTime() - HOLD_TIME_START) / (HOLD_TIME_END - HOLD_TIME_START));
306         unsigned int frameDuration = std::min(CTimeUtils::GetFrameTime() - m_lastHoldTime, 50u); // max 20fps
307
308         // maximal scroll rate is at least 30 items per second, and at most (item_rows/7) items per second
309         //  i.e. timed to take 7 seconds to traverse the list at full speed.
310         // minimal scroll rate is at least 10 items per second
311         float maxSpeed = std::max(frameDuration * 0.001f * 30, frameDuration * 0.001f * GetRows() / 7);
312         float minSpeed = frameDuration * 0.001f * 10;
313         m_scrollItemsPerFrame += std::max(minSpeed, speed*maxSpeed); // accelerate to max speed
314         m_lastHoldTime = CTimeUtils::GetFrameTime();
315
316         if(m_scrollItemsPerFrame < 1.0f)//not enough hold time accumulated for one step
317           return true;
318
319         while (m_scrollItemsPerFrame >= 1)
320         {
321           if (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_UP)
322             MoveUp(false);
323           else
324             MoveDown(false);
325           m_scrollItemsPerFrame--;
326         }
327         return true;
328       }
329       else
330       {
331         //if HOLD_TIME_START is reached we need
332         //a sane initial value for calculating m_scrollItemsPerPage
333         m_lastHoldTime = CTimeUtils::GetFrameTime();
334         m_scrollItemsPerFrame = 0.0f;
335         return CGUIControl::OnAction(action);
336       }
337     }
338     break;
339
340   case ACTION_FIRST_PAGE:
341     SelectItem(0);
342     return true;
343
344   case ACTION_LAST_PAGE:
345     if (m_items.size())
346       SelectItem(m_items.size() - 1);
347     return true;
348
349   case ACTION_NEXT_LETTER:
350     {
351       OnNextLetter();
352       return true;
353     }
354     break;
355   case ACTION_PREV_LETTER:
356     {
357       OnPrevLetter();
358       return true;
359     }
360     break;
361   case ACTION_JUMP_SMS2:
362   case ACTION_JUMP_SMS3:
363   case ACTION_JUMP_SMS4:
364   case ACTION_JUMP_SMS5:
365   case ACTION_JUMP_SMS6:
366   case ACTION_JUMP_SMS7:
367   case ACTION_JUMP_SMS8:
368   case ACTION_JUMP_SMS9:
369     {
370       OnJumpSMS(action.GetID() - ACTION_JUMP_SMS2 + 2);
371       return true;
372     }
373     break;
374
375   default:
376     if (action.GetID())
377     {
378       return OnClick(action.GetID());
379     }
380   }
381   return false;
382 }
383
384 bool CGUIBaseContainer::OnMessage(CGUIMessage& message)
385 {
386   if (message.GetControlId() == GetID() )
387   {
388     if (!m_listProvider)
389     {
390       if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
391       { // bind our items
392         Reset();
393         CFileItemList *items = (CFileItemList *)message.GetPointer();
394         for (int i = 0; i < items->Size(); i++)
395           m_items.push_back(items->Get(i));
396         UpdateLayout(true); // true to refresh all items
397         UpdateScrollByLetter();
398         SelectItem(message.GetParam1());
399         return true;
400       }
401       else if (message.GetMessage() == GUI_MSG_LABEL_RESET)
402       {
403         Reset();
404         SetPageControlRange();
405         return true;
406       }
407     }
408     if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
409     {
410       SelectItem(message.GetParam1());
411       return true;
412     }
413     else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
414     {
415       message.SetParam1(GetSelectedItem());
416       return true;
417     }
418     else if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
419     {
420       if (message.GetSenderId() == m_pageControl && IsVisible())
421       { // update our page if we're visible - not much point otherwise
422         if ((int)message.GetParam1() != GetOffset())
423           m_pageChangeTimer.StartZero();
424         ScrollToOffset(message.GetParam1());
425         return true;
426       }
427     }
428     else if (message.GetMessage() == GUI_MSG_REFRESH_LIST)
429     { // update our list contents
430       for (unsigned int i = 0; i < m_items.size(); ++i)
431         m_items[i]->SetInvalid();
432     }
433     else if (message.GetMessage() == GUI_MSG_MOVE_OFFSET)
434     {
435       int count = (int)message.GetParam1();
436       while (count < 0)
437       {
438         MoveUp(true);
439         count++;
440       }
441       while (count > 0)
442       {
443         MoveDown(true);
444         count--;
445       }
446       return true;
447     }
448   }
449   return CGUIControl::OnMessage(message);
450 }
451
452 void CGUIBaseContainer::OnUp()
453 {
454   bool wrapAround = m_actionUp.GetNavigation() == GetID() || !m_actionUp.HasActionsMeetingCondition();
455   if (m_orientation == VERTICAL && MoveUp(wrapAround))
456     return;
457   // with horizontal lists it doesn't make much sense to have multiselect labels
458   CGUIControl::OnUp();
459 }
460
461 void CGUIBaseContainer::OnDown()
462 {
463   bool wrapAround = m_actionDown.GetNavigation() == GetID() || !m_actionDown.HasActionsMeetingCondition();
464   if (m_orientation == VERTICAL && MoveDown(wrapAround))
465     return;
466   // with horizontal lists it doesn't make much sense to have multiselect labels
467   CGUIControl::OnDown();
468 }
469
470 void CGUIBaseContainer::OnLeft()
471 {
472   bool wrapAround = m_actionLeft.GetNavigation() == GetID() || !m_actionLeft.HasActionsMeetingCondition();
473   if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
474     return;
475   else if (m_orientation == VERTICAL)
476   {
477     CGUIListItemLayout *focusedLayout = GetFocusedLayout();
478     if (focusedLayout && focusedLayout->MoveLeft())
479       return;
480   }
481   CGUIControl::OnLeft();
482 }
483
484 void CGUIBaseContainer::OnRight()
485 {
486   bool wrapAround = m_actionRight.GetNavigation() == GetID() || !m_actionRight.HasActionsMeetingCondition();
487   if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
488     return;
489   else if (m_orientation == VERTICAL)
490   {
491     CGUIListItemLayout *focusedLayout = GetFocusedLayout();
492     if (focusedLayout && focusedLayout->MoveRight())
493       return;
494   }
495   CGUIControl::OnRight();
496 }
497
498 void CGUIBaseContainer::OnNextLetter()
499 {
500   int offset = CorrectOffset(GetOffset(), GetCursor());
501   for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
502   {
503     if (m_letterOffsets[i].first > offset)
504     {
505       SelectItem(m_letterOffsets[i].first);
506       return;
507     }
508   }
509 }
510
511 void CGUIBaseContainer::OnPrevLetter()
512 {
513   int offset = CorrectOffset(GetOffset(), GetCursor());
514   if (!m_letterOffsets.size())
515     return;
516   for (int i = (int)m_letterOffsets.size() - 1; i >= 0; i--)
517   {
518     if (m_letterOffsets[i].first < offset)
519     {
520       SelectItem(m_letterOffsets[i].first);
521       return;
522     }
523   }
524 }
525
526 void CGUIBaseContainer::OnJumpLetter(char letter, bool skip /*=false*/)
527 {
528   if (m_matchTimer.GetElapsedMilliseconds() < letter_match_timeout)
529     m_match.push_back(letter);
530   else
531     m_match = StringUtils::Format("%c", letter);
532
533   m_matchTimer.StartZero();
534
535   // we can't jump through letters if we have none
536   if (0 == m_letterOffsets.size())
537     return;
538
539   // find the current letter we're focused on
540   unsigned int offset = CorrectOffset(GetOffset(), GetCursor());
541   unsigned int i      = (offset + ((skip) ? 1 : 0)) % m_items.size();
542   do
543   {
544     CGUIListItemPtr item = m_items[i];
545     if (0 == strnicmp(SortUtils::RemoveArticles(item->GetLabel()).c_str(), m_match.c_str(), m_match.size()))
546     {
547       SelectItem(i);
548       return;
549     }
550     i = (i+1) % m_items.size();
551   } while (i != offset);
552   // no match found - repeat with a single letter
553   if (m_match.size() > 1)
554   {
555     m_match.clear();
556     OnJumpLetter(letter, true);
557   }
558 }
559
560 void CGUIBaseContainer::OnJumpSMS(int letter)
561 {
562   static const char letterMap[8][6] = { "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
563
564   // only 2..9 supported
565   if (letter < 2 || letter > 9 || !m_letterOffsets.size())
566     return;
567
568   const CStdString letters = letterMap[letter - 2];
569   // find where we currently are
570   int offset = CorrectOffset(GetOffset(), GetCursor());
571   unsigned int currentLetter = 0;
572   while (currentLetter + 1 < m_letterOffsets.size() && m_letterOffsets[currentLetter + 1].first <= offset)
573     currentLetter++;
574
575   // now switch to the next letter
576   CStdString current = m_letterOffsets[currentLetter].second;
577   int startPos = (letters.Find(current) + 1) % letters.size();
578   // now jump to letters[startPos], or another one in the same range if possible
579   int pos = startPos;
580   while (true)
581   {
582     // check if we can jump to this letter
583     for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
584     {
585       if (m_letterOffsets[i].second == letters.Mid(pos, 1))
586       {
587         SelectItem(m_letterOffsets[i].first);
588         return;
589       }
590     }
591     pos = (pos + 1) % letters.size();
592     if (pos == startPos)
593       return;
594   }
595 }
596
597 bool CGUIBaseContainer::MoveUp(bool wrapAround)
598 {
599   return true;
600 }
601
602 bool CGUIBaseContainer::MoveDown(bool wrapAround)
603 {
604   return true;
605 }
606
607 // scrolls the said amount
608 void CGUIBaseContainer::Scroll(int amount)
609 {
610   ScrollToOffset(GetOffset() + amount);
611 }
612
613 int CGUIBaseContainer::GetSelectedItem() const
614 {
615   return CorrectOffset(GetOffset(), GetCursor());
616 }
617
618 CGUIListItemPtr CGUIBaseContainer::GetListItem(int offset, unsigned int flag) const
619 {
620   if (!m_items.size())
621     return CGUIListItemPtr();
622   int item = GetSelectedItem() + offset;
623   if (flag & INFOFLAG_LISTITEM_POSITION) // use offset from the first item displayed, taking into account scrolling
624     item = CorrectOffset((int)(m_scroller.GetValue() / m_layout->Size(m_orientation)), offset);
625
626   if (flag & INFOFLAG_LISTITEM_WRAP)
627   {
628     item %= ((int)m_items.size());
629     if (item < 0) item += m_items.size();
630     return m_items[item];
631   }
632   else
633   {
634     if (item >= 0 && item < (int)m_items.size())
635       return m_items[item];
636   }
637   return CGUIListItemPtr();
638 }
639
640 CGUIListItemLayout *CGUIBaseContainer::GetFocusedLayout() const
641 {
642   CGUIListItemPtr item = GetListItem(0);
643   if (item.get()) return item->GetFocusedLayout();
644   return NULL;
645 }
646
647 bool CGUIBaseContainer::OnMouseOver(const CPoint &point)
648 {
649   // select the item under the pointer
650   SelectItemFromPoint(point - CPoint(m_posX, m_posY));
651   return CGUIControl::OnMouseOver(point);
652 }
653
654 EVENT_RESULT CGUIBaseContainer::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
655 {
656   if (event.m_id >= ACTION_MOUSE_LEFT_CLICK && event.m_id <= ACTION_MOUSE_DOUBLE_CLICK)
657   {
658     if (SelectItemFromPoint(point - CPoint(m_posX, m_posY)))
659     {
660       OnClick(event.m_id);
661       return EVENT_RESULT_HANDLED;
662     }
663   }
664   else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
665   {
666     Scroll(-1);
667     return EVENT_RESULT_HANDLED;
668   }
669   else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
670   {
671     Scroll(1);
672     return EVENT_RESULT_HANDLED;
673   }
674   else if (event.m_id == ACTION_GESTURE_NOTIFY)
675   {
676     return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL : EVENT_RESULT_PAN_VERTICAL;
677   }
678   else if (event.m_id == ACTION_GESTURE_BEGIN)
679   { // grab exclusive access
680     CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
681     SendWindowMessage(msg);
682     return EVENT_RESULT_HANDLED;
683   }
684   else if (event.m_id == ACTION_GESTURE_PAN)
685   { // do the drag and validate our offset (corrects for end of scroll)
686     m_scroller.SetValue(m_scroller.GetValue() - ((m_orientation == HORIZONTAL) ? event.m_offsetX : event.m_offsetY));
687     float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
688     int offset = (int)MathUtils::round_int(m_scroller.GetValue() / size);
689     m_lastScrollStartTimer.Stop();
690     m_scrollTimer.Start();
691     SetOffset(offset);
692     ValidateOffset();
693     return EVENT_RESULT_HANDLED;
694   }
695   else if (event.m_id == ACTION_GESTURE_END)
696   { // release exclusive access
697     CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
698     SendWindowMessage(msg);
699     m_scrollTimer.Stop();
700     // and compute the nearest offset from this and scroll there
701     float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
702     float offset = m_scroller.GetValue() / size;
703     int toOffset = (int)MathUtils::round_int(offset);
704     if (toOffset < offset)
705       SetOffset(toOffset+1);
706     else
707       SetOffset(toOffset-1);
708     ScrollToOffset(toOffset);
709     return EVENT_RESULT_HANDLED;
710   }
711   return EVENT_RESULT_UNHANDLED;
712 }
713
714 bool CGUIBaseContainer::OnClick(int actionID)
715 {
716   int subItem = 0;
717   if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
718   {
719     if (m_listProvider)
720     { // "select" action
721       int selected = GetSelectedItem();
722       if (selected >= 0 && selected < (int)m_items.size())
723         m_listProvider->OnClick(m_items[selected]);
724       return true;
725     }
726     // grab the currently focused subitem (if applicable)
727     CGUIListItemLayout *focusedLayout = GetFocusedLayout();
728     if (focusedLayout)
729       subItem = focusedLayout->GetFocusedItem();
730   }
731   // Don't know what to do, so send to our parent window.
732   CGUIMessage msg(GUI_MSG_CLICKED, GetID(), GetParentID(), actionID, subItem);
733   return SendWindowMessage(msg);
734 }
735
736 CStdString CGUIBaseContainer::GetDescription() const
737 {
738   CStdString strLabel;
739   int item = GetSelectedItem();
740   if (item >= 0 && item < (int)m_items.size())
741   {
742     CGUIListItemPtr pItem = m_items[item];
743     if (pItem->m_bIsFolder)
744       strLabel = StringUtils::Format("[%s]", pItem->GetLabel().c_str());
745     else
746       strLabel = pItem->GetLabel();
747   }
748   return strLabel;
749 }
750
751 void CGUIBaseContainer::SetFocus(bool bOnOff)
752 {
753   if (bOnOff != HasFocus())
754   {
755     SetInvalid();
756     m_lastItem.reset();
757   }
758   CGUIControl::SetFocus(bOnOff);
759 }
760
761 void CGUIBaseContainer::SaveStates(vector<CControlState> &states)
762 {
763   if (!m_listProvider || !m_listProvider->AlwaysFocusDefaultItem())
764     states.push_back(CControlState(GetID(), GetSelectedItem()));
765 }
766
767 void CGUIBaseContainer::SetPageControl(int id)
768 {
769   m_pageControl = id;
770 }
771
772 bool CGUIBaseContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
773 {
774   minOffset = 0;
775   maxOffset = GetRows() - m_itemsPerPage;
776   return true;
777 }
778
779 void CGUIBaseContainer::ValidateOffset()
780 {
781 }
782
783 void CGUIBaseContainer::AllocResources()
784 {
785   CGUIControl::AllocResources();
786   CalculateLayout();
787   UpdateListProvider(true);
788   if (m_listProvider)
789     SelectItem(m_listProvider->GetDefaultItem());
790 }
791
792 void CGUIBaseContainer::FreeResources(bool immediately)
793 {
794   CGUIControl::FreeResources(immediately);
795   if (m_listProvider)
796   {
797     Reset();
798     m_listProvider->Reset();
799   }
800   m_scroller.Stop();
801 }
802
803 void CGUIBaseContainer::UpdateLayout(bool updateAllItems)
804 {
805   if (updateAllItems)
806   { // free memory of items
807     for (iItems it = m_items.begin(); it != m_items.end(); ++it)
808       (*it)->FreeMemory();
809   }
810   // and recalculate the layout
811   CalculateLayout();
812   SetPageControlRange();
813   MarkDirtyRegion();
814 }
815
816 void CGUIBaseContainer::SetPageControlRange()
817 {
818   if (m_pageControl)
819   {
820     CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetRows());
821     SendWindowMessage(msg);
822   }
823 }
824
825 void CGUIBaseContainer::UpdatePageControl(int offset)
826 {
827   if (m_pageControl)
828   { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
829     CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, offset);
830     SendWindowMessage(msg);
831   }
832 }
833
834 void CGUIBaseContainer::UpdateVisibility(const CGUIListItem *item)
835 {
836   CGUIControl::UpdateVisibility(item);
837
838   if (!IsVisible() && !CGUIControl::CanFocus())
839     return; // no need to update the content if we're not visible and we can't focus
840
841   // check whether we need to update our layouts
842   if ((m_layout && !m_layout->CheckCondition()) ||
843       (m_focusedLayout && !m_focusedLayout->CheckCondition()))
844   {
845     // and do it
846     int itemIndex = GetSelectedItem();
847     UpdateLayout(true); // true to refresh all items
848     SelectItem(itemIndex);
849   }
850
851   UpdateListProvider();
852 }
853
854 void CGUIBaseContainer::UpdateListProvider(bool refreshItems)
855 {
856   if (m_listProvider)
857   {
858     if (m_listProvider->Update(refreshItems))
859     {
860       // save the current item
861       int currentItem = GetSelectedItem();
862       CGUIListItem *current = (currentItem >= 0 && currentItem < (int)m_items.size()) ? m_items[currentItem].get() : NULL;
863       Reset();
864       m_listProvider->Fetch(m_items);
865       SetPageControlRange();
866       // update the newly selected item
867       bool found = false;
868       for (int i = 0; i < (int)m_items.size(); i++)
869       {
870         if (m_items[i].get() == current)
871         {
872           found = true;
873           if (i != currentItem)
874           {
875             SelectItem(i);
876             break;
877           }
878         }
879       }
880       if (!found && currentItem >= (int)m_items.size())
881         SelectItem(m_items.size()-1);
882       SetInvalid();
883     }
884     // always update the scroll by letter, as the list provider may have altered labels
885     // while not actually changing the list items.
886     UpdateScrollByLetter();
887   }
888 }
889
890 void CGUIBaseContainer::CalculateLayout()
891 {
892   CGUIListItemLayout *oldFocusedLayout = m_focusedLayout;
893   CGUIListItemLayout *oldLayout = m_layout;
894   GetCurrentLayouts();
895
896   // calculate the number of items to display
897   if (!m_focusedLayout || !m_layout)
898     return;
899
900   if (oldLayout == m_layout && oldFocusedLayout == m_focusedLayout)
901     return; // nothing has changed, so don't update stuff
902
903   m_itemsPerPage = std::max((int)((Size() - m_focusedLayout->Size(m_orientation)) / m_layout->Size(m_orientation)) + 1, 1);
904
905   // ensure that the scroll offset is a multiple of our size
906   m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
907 }
908
909 void CGUIBaseContainer::UpdateScrollByLetter()
910 {
911   m_letterOffsets.clear();
912
913   // for scrolling by letter we have an offset table into our vector.
914   CStdString currentMatch;
915   for (unsigned int i = 0; i < m_items.size(); i++)
916   {
917     CGUIListItemPtr item = m_items[i];
918     // The letter offset jumping is only for ASCII characters at present, and
919     // our checks are all done in uppercase
920     CStdString nextLetter;
921     g_charsetConverter.wToUTF8(item->GetSortLabel().Left(1).ToUpper(), nextLetter);
922     if (currentMatch != nextLetter)
923     {
924       currentMatch = nextLetter;
925       m_letterOffsets.push_back(make_pair((int)i, currentMatch));
926     }
927   }
928 }
929
930 unsigned int CGUIBaseContainer::GetRows() const
931 {
932   return m_items.size();
933 }
934
935 inline float CGUIBaseContainer::Size() const
936 {
937   return (m_orientation == HORIZONTAL) ? m_width : m_height;
938 }
939
940 int CGUIBaseContainer::ScrollCorrectionRange() const
941 {
942   int range = m_itemsPerPage / 4;
943   if (range <= 0) range = 1;
944   return range;
945 }
946
947 void CGUIBaseContainer::ScrollToOffset(int offset)
948 {
949   int minOffset, maxOffset;
950   if(GetOffsetRange(minOffset, maxOffset))
951     offset = std::max(minOffset, std::min(offset, maxOffset));
952   float size = (m_layout) ? m_layout->Size(m_orientation) : 10.0f;
953   int range = ScrollCorrectionRange();
954   if (offset * size < m_scroller.GetValue() &&  m_scroller.GetValue() - offset * size > size * range)
955   { // scrolling up, and we're jumping more than 0.5 of a screen
956     m_scroller.SetValue((offset + range) * size);
957   }
958   if (offset * size > m_scroller.GetValue() && offset * size - m_scroller.GetValue() > size * range)
959   { // scrolling down, and we're jumping more than 0.5 of a screen
960     m_scroller.SetValue((offset - range) * size);
961   }
962   m_scroller.ScrollTo(offset * size);
963   m_lastScrollStartTimer.StartZero();
964   if (!m_wasReset)
965   {
966     SetContainerMoving(offset - GetOffset());
967     if (m_scroller.IsScrolling())
968       m_scrollTimer.Start();
969     else
970       m_scrollTimer.Stop();
971   }
972   SetOffset(offset);
973 }
974
975 void CGUIBaseContainer::SetContainerMoving(int direction)
976 {
977   if (direction)
978     g_infoManager.SetContainerMoving(GetID(), direction > 0, m_scroller.IsScrolling());
979 }
980
981 void CGUIBaseContainer::UpdateScrollOffset(unsigned int currentTime)
982 {
983   if (m_scroller.Update(currentTime))
984     MarkDirtyRegion();
985   else if (m_lastScrollStartTimer.GetElapsedMilliseconds() >= SCROLLING_GAP)
986   {
987     m_scrollTimer.Stop();
988     m_lastScrollStartTimer.Stop();
989   }
990 }
991
992 int CGUIBaseContainer::CorrectOffset(int offset, int cursor) const
993 {
994   return offset + cursor;
995 }
996
997 void CGUIBaseContainer::Reset()
998 {
999   m_wasReset = true;
1000   m_items.clear();
1001   m_lastItem.reset();
1002 }
1003
1004 void CGUIBaseContainer::LoadLayout(TiXmlElement *layout)
1005 {
1006   TiXmlElement *itemElement = layout->FirstChildElement("itemlayout");
1007   while (itemElement)
1008   { // we have a new item layout
1009     CGUIListItemLayout itemLayout;
1010     itemLayout.LoadLayout(itemElement, GetParentID(), false);
1011     m_layouts.push_back(itemLayout);
1012     itemElement = itemElement->NextSiblingElement("itemlayout");
1013   }
1014   itemElement = layout->FirstChildElement("focusedlayout");
1015   while (itemElement)
1016   { // we have a new item layout
1017     CGUIListItemLayout itemLayout;
1018     itemLayout.LoadLayout(itemElement, GetParentID(), true);
1019     m_focusedLayouts.push_back(itemLayout);
1020     itemElement = itemElement->NextSiblingElement("focusedlayout");
1021   }
1022 }
1023
1024 void CGUIBaseContainer::LoadListProvider(TiXmlElement *content, int defaultItem, bool defaultAlways)
1025 {
1026   delete m_listProvider;
1027   m_listProvider = IListProvider::Create(content, GetParentID());
1028   if (m_listProvider)
1029     m_listProvider->SetDefaultItem(defaultItem, defaultAlways);
1030 }
1031
1032 void CGUIBaseContainer::SetListProvider(IListProvider *provider)
1033 {
1034   delete m_listProvider;
1035   m_listProvider = provider;
1036   UpdateListProvider(true);
1037 }
1038
1039 void CGUIBaseContainer::SetRenderOffset(const CPoint &offset)
1040 {
1041   m_renderOffset = offset;
1042 }
1043
1044 void CGUIBaseContainer::FreeMemory(int keepStart, int keepEnd)
1045 {
1046   if (keepStart < keepEnd)
1047   { // remove before keepStart and after keepEnd
1048     for (int i = 0; i < keepStart && i < (int)m_items.size(); ++i)
1049       m_items[i]->FreeMemory();
1050     for (int i = std::max(keepEnd + 1, 0); i < (int)m_items.size(); ++i)
1051       m_items[i]->FreeMemory();
1052   }
1053   else
1054   { // wrapping
1055     for (int i = std::max(keepEnd + 1, 0); i < keepStart && i < (int)m_items.size(); ++i)
1056       m_items[i]->FreeMemory();
1057   }
1058 }
1059
1060 bool CGUIBaseContainer::InsideLayout(const CGUIListItemLayout *layout, const CPoint &point) const
1061 {
1062   if (!layout) return false;
1063   if ((m_orientation == VERTICAL && (layout->Size(HORIZONTAL) > 1) && point.x > layout->Size(HORIZONTAL)) ||
1064       (m_orientation == HORIZONTAL && (layout->Size(VERTICAL) > 1)&& point.y > layout->Size(VERTICAL)))
1065     return false;
1066   return true;
1067 }
1068
1069 #ifdef _DEBUG
1070 void CGUIBaseContainer::DumpTextureUse()
1071 {
1072   CLog::Log(LOGDEBUG, "%s for container %u", __FUNCTION__, GetID());
1073   for (unsigned int i = 0; i < m_items.size(); ++i)
1074   {
1075     CGUIListItemPtr item = m_items[i];
1076     if (item->GetFocusedLayout()) item->GetFocusedLayout()->DumpTextureUse();
1077     if (item->GetLayout()) item->GetLayout()->DumpTextureUse();
1078   }
1079 }
1080 #endif
1081
1082 bool CGUIBaseContainer::GetCondition(int condition, int data) const
1083 {
1084   switch (condition)
1085   {
1086   case CONTAINER_ROW:
1087     return (m_orientation == VERTICAL) ? (GetCursor() == data) : true;
1088   case CONTAINER_COLUMN:
1089     return (m_orientation == HORIZONTAL) ? (GetCursor() == data) : true;
1090   case CONTAINER_POSITION:
1091     return (GetCursor() == data);
1092   case CONTAINER_HAS_NEXT:
1093     return (HasNextPage());
1094   case CONTAINER_HAS_PREVIOUS:
1095     return (HasPreviousPage());
1096   case CONTAINER_SUBITEM:
1097     {
1098       CGUIListItemLayout *layout = GetFocusedLayout();
1099       return layout ? (layout->GetFocusedItem() == (unsigned int)data) : false;
1100     }
1101   case CONTAINER_SCROLLING:
1102     return (m_scrollTimer.GetElapsedMilliseconds() > std::max(m_scroller.GetDuration(), SCROLLING_THRESHOLD) || m_pageChangeTimer.IsRunning());
1103   default:
1104     return false;
1105   }
1106 }
1107
1108 void CGUIBaseContainer::GetCurrentLayouts()
1109 {
1110   m_layout = NULL;
1111   for (unsigned int i = 0; i < m_layouts.size(); i++)
1112   {
1113     if (m_layouts[i].CheckCondition())
1114     {
1115       m_layout = &m_layouts[i];
1116       break;
1117     }
1118   }
1119   if (!m_layout && m_layouts.size())
1120     m_layout = &m_layouts[0];  // failsafe
1121
1122   m_focusedLayout = NULL;
1123   for (unsigned int i = 0; i < m_focusedLayouts.size(); i++)
1124   {
1125     if (m_focusedLayouts[i].CheckCondition())
1126     {
1127       m_focusedLayout = &m_focusedLayouts[i];
1128       break;
1129     }
1130   }
1131   if (!m_focusedLayout && m_focusedLayouts.size())
1132     m_focusedLayout = &m_focusedLayouts[0];  // failsafe
1133 }
1134
1135 bool CGUIBaseContainer::HasNextPage() const
1136 {
1137   return false;
1138 }
1139
1140 bool CGUIBaseContainer::HasPreviousPage() const
1141 {
1142   return false;
1143 }
1144
1145 CStdString CGUIBaseContainer::GetLabel(int info) const
1146 {
1147   CStdString label;
1148   switch (info)
1149   {
1150   case CONTAINER_NUM_PAGES:
1151     label = StringUtils::Format("%u", (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage);
1152     break;
1153   case CONTAINER_CURRENT_PAGE:
1154     label = StringUtils::Format("%u", GetCurrentPage());
1155     break;
1156   case CONTAINER_POSITION:
1157     label = StringUtils::Format("%i", GetCursor());
1158     break;
1159   case CONTAINER_NUM_ITEMS:
1160     {
1161       unsigned int numItems = GetNumItems();
1162       if (numItems && m_items[0]->IsFileItem() && (boost::static_pointer_cast<CFileItem>(m_items[0]))->IsParentFolder())
1163         label = StringUtils::Format("%u", numItems-1);
1164       else
1165         label = StringUtils::Format("%u", numItems);
1166     }
1167     break;
1168   default:
1169       break;
1170   }
1171   return label;
1172 }
1173
1174 int CGUIBaseContainer::GetCurrentPage() const
1175 {
1176   if (GetOffset() + m_itemsPerPage >= (int)GetRows())  // last page
1177     return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
1178   return GetOffset() / m_itemsPerPage + 1;
1179 }
1180
1181 void CGUIBaseContainer::GetCacheOffsets(int &cacheBefore, int &cacheAfter) const
1182 {
1183   if (m_scroller.IsScrollingDown())
1184   {
1185     cacheBefore = 0;
1186     cacheAfter = m_cacheItems;
1187   }
1188   else if (m_scroller.IsScrollingUp())
1189   {
1190     cacheBefore = m_cacheItems;
1191     cacheAfter = 0;
1192   }
1193   else
1194   {
1195     cacheBefore = m_cacheItems / 2;
1196     cacheAfter = m_cacheItems / 2;
1197   }
1198 }
1199
1200 void CGUIBaseContainer::SetCursor(int cursor)
1201 {
1202   m_cursor = cursor;
1203 }
1204
1205 void CGUIBaseContainer::SetOffset(int offset)
1206 {
1207   if (m_offset != offset)
1208     MarkDirtyRegion();
1209   m_offset = offset;
1210 }
1211
1212 bool CGUIBaseContainer::CanFocus() const
1213 {
1214   if (CGUIControl::CanFocus())
1215   {
1216     /*
1217      We allow focus if we have items available or if we have a list provider
1218      that's in the process of updating.
1219      */
1220     return !m_items.empty() || (m_listProvider && m_listProvider->IsUpdating());
1221   }
1222   return false;
1223 }
1224
1225 void CGUIBaseContainer::OnFocus()
1226 {
1227   if (m_listProvider && m_listProvider->AlwaysFocusDefaultItem())
1228     SelectItem(m_listProvider->GetDefaultItem());
1229
1230   CGUIControl::OnFocus();
1231 }