2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
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"
32 #include "utils/MathUtils.h"
33 #include "utils/XBMCTinyXML.h"
34 #include "listproviders/IListProvider.h"
38 #define HOLD_TIME_START 100
39 #define HOLD_TIME_END 3000
40 #define SCROLLING_GAP 200U
41 #define SCROLLING_THRESHOLD 300U
43 CGUIBaseContainer::CGUIBaseContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
44 : IGUIContainer(parentID, controlID, posX, posY, width, height)
45 , m_scroller(scroller)
52 m_orientation = orientation;
53 m_analogScrollCount = 0;
56 m_focusedLayout = NULL;
57 m_cacheItems = preloadItems;
58 m_scrollItemsPerFrame = 0.0f;
59 m_type = VIEW_TYPE_NONE;
60 m_listProvider = NULL;
63 CGUIBaseContainer::~CGUIBaseContainer(void)
65 delete m_listProvider;
68 void CGUIBaseContainer::DoProcess(unsigned int currentTime, CDirtyRegionList &dirtyregions)
70 CGUIControl::DoProcess(currentTime, dirtyregions);
72 if (m_pageChangeTimer.GetElapsedMilliseconds() > 200)
73 m_pageChangeTimer.Stop();
77 void CGUIBaseContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
84 if (!m_layout || !m_focusedLayout) return;
86 UpdateScrollOffset(currentTime);
88 int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
90 int cacheBefore, cacheAfter;
91 GetCacheOffsets(cacheBefore, cacheAfter);
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));
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;
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);
107 end += cacheAfter * m_layout->Size(m_orientation);
109 int current = offset - cacheBefore;
110 while (pos < end && m_items.size())
112 int itemNo = CorrectOffset(current, 0);
113 if (itemNo >= (int)m_items.size())
115 bool focused = (current == GetOffset() + GetCursor());
118 CGUIListItemPtr item = m_items[itemNo];
120 if (m_orientation == VERTICAL)
121 ProcessItem(origin.x, pos, item, focused, currentTime, dirtyregions);
123 ProcessItem(pos, origin.y, item, focused, currentTime, dirtyregions);
125 // increment our position
126 pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
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));
134 CGUIControl::Process(currentTime, dirtyregions);
137 void CGUIBaseContainer::ProcessItem(float posX, float posY, CGUIListItemPtr& item, bool focused, unsigned int currentTime, CDirtyRegionList &dirtyregions)
139 if (!m_focusedLayout || !m_layout) return;
142 g_graphicsContext.SetOrigin(posX, posY);
148 if (!item->GetFocusedLayout())
150 CGUIListItemLayout *layout = new CGUIListItemLayout(*m_focusedLayout);
151 item->SetFocusedLayout(layout);
153 if (item->GetFocusedLayout())
155 if (item != m_lastItem || !HasFocus())
157 item->GetFocusedLayout()->SetFocusedItem(0);
159 if (item != m_lastItem && HasFocus())
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);
167 item->GetFocusedLayout()->Process(item.get(), m_parentID, currentTime, dirtyregions);
173 if (item->GetFocusedLayout())
174 item->GetFocusedLayout()->SetFocusedItem(0); // focus is not set
175 if (!item->GetLayout())
177 CGUIListItemLayout *layout = new CGUIListItemLayout(*m_layout);
178 item->SetLayout(layout);
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);
186 g_graphicsContext.RestoreOrigin();
189 void CGUIBaseContainer::Render()
191 if (!m_layout || !m_focusedLayout) return;
193 int offset = (int)floorf(m_scroller.GetValue() / m_layout->Size(m_orientation));
195 int cacheBefore, cacheAfter;
196 GetCacheOffsets(cacheBefore, cacheAfter);
198 if (g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height))
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;
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);
210 end += cacheAfter * m_layout->Size(m_orientation);
212 float focusedPos = 0;
213 CGUIListItemPtr focusedItem;
214 int current = offset - cacheBefore;
215 while (pos < end && m_items.size())
217 int itemNo = CorrectOffset(current, 0);
218 if (itemNo >= (int)m_items.size())
220 bool focused = (current == GetOffset() + GetCursor());
223 CGUIListItemPtr item = m_items[itemNo];
232 if (m_orientation == VERTICAL)
233 RenderItem(origin.x, pos, item.get(), false);
235 RenderItem(pos, origin.y, item.get(), false);
238 // increment our position
239 pos += focused ? m_focusedLayout->Size(m_orientation) : m_layout->Size(m_orientation);
242 // render focused item last so it can overlap other items
245 if (m_orientation == VERTICAL)
246 RenderItem(origin.x, focusedPos, focusedItem.get(), true);
248 RenderItem(focusedPos, origin.y, focusedItem.get(), true);
251 g_graphicsContext.RestoreClipRegion();
254 CGUIControl::Render();
258 void CGUIBaseContainer::RenderItem(float posX, float posY, CGUIListItem *item, bool focused)
260 if (!m_focusedLayout || !m_layout) return;
263 g_graphicsContext.SetOrigin(posX, posY);
267 if (item->GetFocusedLayout())
268 item->GetFocusedLayout()->Render(item, m_parentID);
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);
277 g_graphicsContext.RestoreOrigin();
280 bool CGUIBaseContainer::OnAction(const CAction &action)
282 if (action.GetID() >= KEY_ASCII)
284 OnJumpLetter((char)(action.GetID() & 0xff));
287 // stop the timer on any other action
290 switch (action.GetID())
292 case ACTION_MOVE_LEFT:
293 case ACTION_MOVE_RIGHT:
294 case ACTION_MOVE_DOWN:
296 case ACTION_NAV_BACK:
297 case ACTION_PREVIOUS_MENU:
299 if (!HasFocus()) return false;
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
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();
316 if(m_scrollItemsPerFrame < 1.0f)//not enough hold time accumulated for one step
319 while (m_scrollItemsPerFrame >= 1)
321 if (action.GetID() == ACTION_MOVE_LEFT || action.GetID() == ACTION_MOVE_UP)
325 m_scrollItemsPerFrame--;
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);
340 case ACTION_FIRST_PAGE:
344 case ACTION_LAST_PAGE:
346 SelectItem(m_items.size() - 1);
349 case ACTION_NEXT_LETTER:
355 case ACTION_PREV_LETTER:
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:
370 OnJumpSMS(action.GetID() - ACTION_JUMP_SMS2 + 2);
378 return OnClick(action.GetID());
384 bool CGUIBaseContainer::OnMessage(CGUIMessage& message)
386 if (message.GetControlId() == GetID() )
390 if (message.GetMessage() == GUI_MSG_LABEL_BIND && message.GetPointer())
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());
401 else if (message.GetMessage() == GUI_MSG_LABEL_RESET)
404 SetPageControlRange();
408 if (message.GetMessage() == GUI_MSG_ITEM_SELECT)
410 SelectItem(message.GetParam1());
413 else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
415 message.SetParam1(GetSelectedItem());
418 else if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
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());
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();
433 else if (message.GetMessage() == GUI_MSG_MOVE_OFFSET)
435 int count = (int)message.GetParam1();
449 return CGUIControl::OnMessage(message);
452 void CGUIBaseContainer::OnUp()
454 bool wrapAround = m_actionUp.GetNavigation() == GetID() || !m_actionUp.HasActionsMeetingCondition();
455 if (m_orientation == VERTICAL && MoveUp(wrapAround))
457 // with horizontal lists it doesn't make much sense to have multiselect labels
461 void CGUIBaseContainer::OnDown()
463 bool wrapAround = m_actionDown.GetNavigation() == GetID() || !m_actionDown.HasActionsMeetingCondition();
464 if (m_orientation == VERTICAL && MoveDown(wrapAround))
466 // with horizontal lists it doesn't make much sense to have multiselect labels
467 CGUIControl::OnDown();
470 void CGUIBaseContainer::OnLeft()
472 bool wrapAround = m_actionLeft.GetNavigation() == GetID() || !m_actionLeft.HasActionsMeetingCondition();
473 if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
475 else if (m_orientation == VERTICAL)
477 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
478 if (focusedLayout && focusedLayout->MoveLeft())
481 CGUIControl::OnLeft();
484 void CGUIBaseContainer::OnRight()
486 bool wrapAround = m_actionRight.GetNavigation() == GetID() || !m_actionRight.HasActionsMeetingCondition();
487 if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
489 else if (m_orientation == VERTICAL)
491 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
492 if (focusedLayout && focusedLayout->MoveRight())
495 CGUIControl::OnRight();
498 void CGUIBaseContainer::OnNextLetter()
500 int offset = CorrectOffset(GetOffset(), GetCursor());
501 for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
503 if (m_letterOffsets[i].first > offset)
505 SelectItem(m_letterOffsets[i].first);
511 void CGUIBaseContainer::OnPrevLetter()
513 int offset = CorrectOffset(GetOffset(), GetCursor());
514 if (!m_letterOffsets.size())
516 for (int i = (int)m_letterOffsets.size() - 1; i >= 0; i--)
518 if (m_letterOffsets[i].first < offset)
520 SelectItem(m_letterOffsets[i].first);
526 void CGUIBaseContainer::OnJumpLetter(char letter, bool skip /*=false*/)
528 if (m_matchTimer.GetElapsedMilliseconds() < letter_match_timeout)
529 m_match.push_back(letter);
531 m_match = StringUtils::Format("%c", letter);
533 m_matchTimer.StartZero();
535 // we can't jump through letters if we have none
536 if (0 == m_letterOffsets.size())
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();
544 CGUIListItemPtr item = m_items[i];
545 if (0 == strnicmp(SortUtils::RemoveArticles(item->GetLabel()).c_str(), m_match.c_str(), m_match.size()))
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)
556 OnJumpLetter(letter, true);
560 void CGUIBaseContainer::OnJumpSMS(int letter)
562 static const char letterMap[8][6] = { "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
564 // only 2..9 supported
565 if (letter < 2 || letter > 9 || !m_letterOffsets.size())
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)
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
582 // check if we can jump to this letter
583 for (unsigned int i = 0; i < m_letterOffsets.size(); i++)
585 if (m_letterOffsets[i].second == letters.Mid(pos, 1))
587 SelectItem(m_letterOffsets[i].first);
591 pos = (pos + 1) % letters.size();
597 bool CGUIBaseContainer::MoveUp(bool wrapAround)
602 bool CGUIBaseContainer::MoveDown(bool wrapAround)
607 // scrolls the said amount
608 void CGUIBaseContainer::Scroll(int amount)
610 ScrollToOffset(GetOffset() + amount);
613 int CGUIBaseContainer::GetSelectedItem() const
615 return CorrectOffset(GetOffset(), GetCursor());
618 CGUIListItemPtr CGUIBaseContainer::GetListItem(int offset, unsigned int flag) const
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);
626 if (flag & INFOFLAG_LISTITEM_WRAP)
628 item %= ((int)m_items.size());
629 if (item < 0) item += m_items.size();
630 return m_items[item];
634 if (item >= 0 && item < (int)m_items.size())
635 return m_items[item];
637 return CGUIListItemPtr();
640 CGUIListItemLayout *CGUIBaseContainer::GetFocusedLayout() const
642 CGUIListItemPtr item = GetListItem(0);
643 if (item.get()) return item->GetFocusedLayout();
647 bool CGUIBaseContainer::OnMouseOver(const CPoint &point)
649 // select the item under the pointer
650 SelectItemFromPoint(point - CPoint(m_posX, m_posY));
651 return CGUIControl::OnMouseOver(point);
654 EVENT_RESULT CGUIBaseContainer::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
656 if (event.m_id >= ACTION_MOUSE_LEFT_CLICK && event.m_id <= ACTION_MOUSE_DOUBLE_CLICK)
658 if (SelectItemFromPoint(point - CPoint(m_posX, m_posY)))
661 return EVENT_RESULT_HANDLED;
664 else if (event.m_id == ACTION_MOUSE_WHEEL_UP)
667 return EVENT_RESULT_HANDLED;
669 else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN)
672 return EVENT_RESULT_HANDLED;
674 else if (event.m_id == ACTION_GESTURE_NOTIFY)
676 return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL : EVENT_RESULT_PAN_VERTICAL;
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;
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();
693 return EVENT_RESULT_HANDLED;
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);
707 SetOffset(toOffset-1);
708 ScrollToOffset(toOffset);
709 return EVENT_RESULT_HANDLED;
711 return EVENT_RESULT_UNHANDLED;
714 bool CGUIBaseContainer::OnClick(int actionID)
717 if (actionID == ACTION_SELECT_ITEM || actionID == ACTION_MOUSE_LEFT_CLICK)
721 int selected = GetSelectedItem();
722 if (selected >= 0 && selected < (int)m_items.size())
723 m_listProvider->OnClick(m_items[selected]);
726 // grab the currently focused subitem (if applicable)
727 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
729 subItem = focusedLayout->GetFocusedItem();
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);
736 CStdString CGUIBaseContainer::GetDescription() const
739 int item = GetSelectedItem();
740 if (item >= 0 && item < (int)m_items.size())
742 CGUIListItemPtr pItem = m_items[item];
743 if (pItem->m_bIsFolder)
744 strLabel = StringUtils::Format("[%s]", pItem->GetLabel().c_str());
746 strLabel = pItem->GetLabel();
751 void CGUIBaseContainer::SetFocus(bool bOnOff)
753 if (bOnOff != HasFocus())
758 CGUIControl::SetFocus(bOnOff);
761 void CGUIBaseContainer::SaveStates(vector<CControlState> &states)
763 if (!m_listProvider || !m_listProvider->AlwaysFocusDefaultItem())
764 states.push_back(CControlState(GetID(), GetSelectedItem()));
767 void CGUIBaseContainer::SetPageControl(int id)
772 bool CGUIBaseContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
775 maxOffset = GetRows() - m_itemsPerPage;
779 void CGUIBaseContainer::ValidateOffset()
783 void CGUIBaseContainer::AllocResources()
785 CGUIControl::AllocResources();
787 UpdateListProvider(true);
789 SelectItem(m_listProvider->GetDefaultItem());
792 void CGUIBaseContainer::FreeResources(bool immediately)
794 CGUIControl::FreeResources(immediately);
798 m_listProvider->Reset();
803 void CGUIBaseContainer::UpdateLayout(bool updateAllItems)
806 { // free memory of items
807 for (iItems it = m_items.begin(); it != m_items.end(); ++it)
810 // and recalculate the layout
812 SetPageControlRange();
816 void CGUIBaseContainer::SetPageControlRange()
820 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetRows());
821 SendWindowMessage(msg);
825 void CGUIBaseContainer::UpdatePageControl(int offset)
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);
834 void CGUIBaseContainer::UpdateVisibility(const CGUIListItem *item)
836 CGUIControl::UpdateVisibility(item);
838 if (!IsVisible() && !CGUIControl::CanFocus())
839 return; // no need to update the content if we're not visible and we can't focus
841 // check whether we need to update our layouts
842 if ((m_layout && !m_layout->CheckCondition()) ||
843 (m_focusedLayout && !m_focusedLayout->CheckCondition()))
846 int itemIndex = GetSelectedItem();
847 UpdateLayout(true); // true to refresh all items
848 SelectItem(itemIndex);
851 UpdateListProvider();
854 void CGUIBaseContainer::UpdateListProvider(bool refreshItems)
858 if (m_listProvider->Update(refreshItems))
860 // save the current item
861 int currentItem = GetSelectedItem();
862 CGUIListItem *current = (currentItem >= 0 && currentItem < (int)m_items.size()) ? m_items[currentItem].get() : NULL;
864 m_listProvider->Fetch(m_items);
865 SetPageControlRange();
866 // update the newly selected item
868 for (int i = 0; i < (int)m_items.size(); i++)
870 if (m_items[i].get() == current)
873 if (i != currentItem)
880 if (!found && currentItem >= (int)m_items.size())
881 SelectItem(m_items.size()-1);
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();
890 void CGUIBaseContainer::CalculateLayout()
892 CGUIListItemLayout *oldFocusedLayout = m_focusedLayout;
893 CGUIListItemLayout *oldLayout = m_layout;
896 // calculate the number of items to display
897 if (!m_focusedLayout || !m_layout)
900 if (oldLayout == m_layout && oldFocusedLayout == m_focusedLayout)
901 return; // nothing has changed, so don't update stuff
903 m_itemsPerPage = std::max((int)((Size() - m_focusedLayout->Size(m_orientation)) / m_layout->Size(m_orientation)) + 1, 1);
905 // ensure that the scroll offset is a multiple of our size
906 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
909 void CGUIBaseContainer::UpdateScrollByLetter()
911 m_letterOffsets.clear();
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++)
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)
924 currentMatch = nextLetter;
925 m_letterOffsets.push_back(make_pair((int)i, currentMatch));
930 unsigned int CGUIBaseContainer::GetRows() const
932 return m_items.size();
935 inline float CGUIBaseContainer::Size() const
937 return (m_orientation == HORIZONTAL) ? m_width : m_height;
940 int CGUIBaseContainer::ScrollCorrectionRange() const
942 int range = m_itemsPerPage / 4;
943 if (range <= 0) range = 1;
947 void CGUIBaseContainer::ScrollToOffset(int offset)
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);
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);
962 m_scroller.ScrollTo(offset * size);
963 m_lastScrollStartTimer.StartZero();
966 SetContainerMoving(offset - GetOffset());
967 if (m_scroller.IsScrolling())
968 m_scrollTimer.Start();
970 m_scrollTimer.Stop();
975 void CGUIBaseContainer::SetContainerMoving(int direction)
978 g_infoManager.SetContainerMoving(GetID(), direction > 0, m_scroller.IsScrolling());
981 void CGUIBaseContainer::UpdateScrollOffset(unsigned int currentTime)
983 if (m_scroller.Update(currentTime))
985 else if (m_lastScrollStartTimer.GetElapsedMilliseconds() >= SCROLLING_GAP)
987 m_scrollTimer.Stop();
988 m_lastScrollStartTimer.Stop();
992 int CGUIBaseContainer::CorrectOffset(int offset, int cursor) const
994 return offset + cursor;
997 void CGUIBaseContainer::Reset()
1004 void CGUIBaseContainer::LoadLayout(TiXmlElement *layout)
1006 TiXmlElement *itemElement = layout->FirstChildElement("itemlayout");
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");
1014 itemElement = layout->FirstChildElement("focusedlayout");
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");
1024 void CGUIBaseContainer::LoadListProvider(TiXmlElement *content, int defaultItem, bool defaultAlways)
1026 delete m_listProvider;
1027 m_listProvider = IListProvider::Create(content, GetParentID());
1029 m_listProvider->SetDefaultItem(defaultItem, defaultAlways);
1032 void CGUIBaseContainer::SetListProvider(IListProvider *provider)
1034 delete m_listProvider;
1035 m_listProvider = provider;
1036 UpdateListProvider(true);
1039 void CGUIBaseContainer::SetRenderOffset(const CPoint &offset)
1041 m_renderOffset = offset;
1044 void CGUIBaseContainer::FreeMemory(int keepStart, int keepEnd)
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();
1055 for (int i = std::max(keepEnd + 1, 0); i < keepStart && i < (int)m_items.size(); ++i)
1056 m_items[i]->FreeMemory();
1060 bool CGUIBaseContainer::InsideLayout(const CGUIListItemLayout *layout, const CPoint &point) const
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)))
1070 void CGUIBaseContainer::DumpTextureUse()
1072 CLog::Log(LOGDEBUG, "%s for container %u", __FUNCTION__, GetID());
1073 for (unsigned int i = 0; i < m_items.size(); ++i)
1075 CGUIListItemPtr item = m_items[i];
1076 if (item->GetFocusedLayout()) item->GetFocusedLayout()->DumpTextureUse();
1077 if (item->GetLayout()) item->GetLayout()->DumpTextureUse();
1082 bool CGUIBaseContainer::GetCondition(int condition, int data) const
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:
1098 CGUIListItemLayout *layout = GetFocusedLayout();
1099 return layout ? (layout->GetFocusedItem() == (unsigned int)data) : false;
1101 case CONTAINER_SCROLLING:
1102 return (m_scrollTimer.GetElapsedMilliseconds() > std::max(m_scroller.GetDuration(), SCROLLING_THRESHOLD) || m_pageChangeTimer.IsRunning());
1108 void CGUIBaseContainer::GetCurrentLayouts()
1111 for (unsigned int i = 0; i < m_layouts.size(); i++)
1113 if (m_layouts[i].CheckCondition())
1115 m_layout = &m_layouts[i];
1119 if (!m_layout && m_layouts.size())
1120 m_layout = &m_layouts[0]; // failsafe
1122 m_focusedLayout = NULL;
1123 for (unsigned int i = 0; i < m_focusedLayouts.size(); i++)
1125 if (m_focusedLayouts[i].CheckCondition())
1127 m_focusedLayout = &m_focusedLayouts[i];
1131 if (!m_focusedLayout && m_focusedLayouts.size())
1132 m_focusedLayout = &m_focusedLayouts[0]; // failsafe
1135 bool CGUIBaseContainer::HasNextPage() const
1140 bool CGUIBaseContainer::HasPreviousPage() const
1145 CStdString CGUIBaseContainer::GetLabel(int info) const
1150 case CONTAINER_NUM_PAGES:
1151 label = StringUtils::Format("%u", (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage);
1153 case CONTAINER_CURRENT_PAGE:
1154 label = StringUtils::Format("%u", GetCurrentPage());
1156 case CONTAINER_POSITION:
1157 label = StringUtils::Format("%i", GetCursor());
1159 case CONTAINER_NUM_ITEMS:
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);
1165 label = StringUtils::Format("%u", numItems);
1174 int CGUIBaseContainer::GetCurrentPage() const
1176 if (GetOffset() + m_itemsPerPage >= (int)GetRows()) // last page
1177 return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
1178 return GetOffset() / m_itemsPerPage + 1;
1181 void CGUIBaseContainer::GetCacheOffsets(int &cacheBefore, int &cacheAfter) const
1183 if (m_scroller.IsScrollingDown())
1186 cacheAfter = m_cacheItems;
1188 else if (m_scroller.IsScrollingUp())
1190 cacheBefore = m_cacheItems;
1195 cacheBefore = m_cacheItems / 2;
1196 cacheAfter = m_cacheItems / 2;
1200 void CGUIBaseContainer::SetCursor(int cursor)
1205 void CGUIBaseContainer::SetOffset(int offset)
1207 if (m_offset != offset)
1212 bool CGUIBaseContainer::CanFocus() const
1214 if (CGUIControl::CanFocus())
1217 We allow focus if we have items available or if we have a list provider
1218 that's in the process of updating.
1220 return !m_items.empty() || (m_listProvider && m_listProvider->IsUpdating());
1225 void CGUIBaseContainer::OnFocus()
1227 if (m_listProvider && m_listProvider->AlwaysFocusDefaultItem())
1228 SelectItem(m_listProvider->GetDefaultItem());
1230 CGUIControl::OnFocus();