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 "GUIControlGroupList.h"
23 #include "GUIInfoManager.h"
24 #include "GUIControlProfiler.h"
25 #include "GUIFont.h" // for XBFONT_* definitions
27 CGUIControlGroupList::CGUIControlGroupList(int parentID, int controlID, float posX, float posY, float width, float height, float itemGap, int pageControl, ORIENTATION orientation, bool useControlPositions, uint32_t alignment, const CScroller& scroller)
28 : CGUIControlGroup(parentID, controlID, posX, posY, width, height)
29 , m_scroller(scroller)
32 m_pageControl = pageControl;
34 m_orientation = orientation;
35 m_alignment = alignment;
36 m_useControlPositions = useControlPositions;
37 ControlType = GUICONTROL_GROUPLIST;
41 CGUIControlGroupList::~CGUIControlGroupList(void)
45 void CGUIControlGroupList::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
47 if (m_scroller.Update(currentTime))
50 // first we update visibility of all our items, to ensure our size and
51 // alignment computations are correct.
52 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
54 CGUIControl *control = *it;
55 GUIPROFILER_VISIBILITY_BEGIN(control);
56 control->UpdateVisibility();
57 GUIPROFILER_VISIBILITY_END(control);
63 CGUIMessage message(GUI_MSG_LABEL_RESET, GetParentID(), m_pageControl, (int)Size(), (int)m_totalSize);
64 SendWindowMessage(message);
65 CGUIMessage message2(GUI_MSG_ITEM_SELECT, GetParentID(), m_pageControl, (int)m_scroller.GetValue());
66 SendWindowMessage(message2);
68 // we run through the controls, rendering as we go
69 float pos = GetAlignOffset();
70 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
72 // note we render all controls, even if they're offscreen, as then they'll be updated
73 // with respect to animations
74 CGUIControl *control = *it;
75 if (m_orientation == VERTICAL)
76 g_graphicsContext.SetOrigin(m_posX, m_posY + pos - m_scroller.GetValue());
78 g_graphicsContext.SetOrigin(m_posX + pos - m_scroller.GetValue(), m_posY);
79 control->DoProcess(currentTime, dirtyregions);
81 if (control->IsVisible())
82 pos += Size(control) + m_itemGap;
83 g_graphicsContext.RestoreOrigin();
85 CGUIControl::Process(currentTime, dirtyregions);
88 void CGUIControlGroupList::Render()
90 // we run through the controls, rendering as we go
91 bool render(g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height));
92 float pos = GetAlignOffset();
94 CGUIControl *focusedControl = NULL;
95 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
97 // note we render all controls, even if they're offscreen, as then they'll be updated
98 // with respect to animations
99 CGUIControl *control = *it;
100 if (m_renderFocusedLast && control->HasFocus())
102 focusedControl = control;
107 if (m_orientation == VERTICAL)
108 g_graphicsContext.SetOrigin(m_posX, m_posY + pos - m_scroller.GetValue());
110 g_graphicsContext.SetOrigin(m_posX + pos - m_scroller.GetValue(), m_posY);
113 if (control->IsVisible())
114 pos += Size(control) + m_itemGap;
115 g_graphicsContext.RestoreOrigin();
119 if (m_orientation == VERTICAL)
120 g_graphicsContext.SetOrigin(m_posX, m_posY + focusedPos - m_scroller.GetValue());
122 g_graphicsContext.SetOrigin(m_posX + focusedPos - m_scroller.GetValue(), m_posY);
123 focusedControl->DoRender();
125 if (render) g_graphicsContext.RestoreClipRegion();
126 CGUIControl::Render();
129 bool CGUIControlGroupList::OnMessage(CGUIMessage& message)
131 switch (message.GetMessage() )
133 case GUI_MSG_FOCUSED:
134 { // a control has been focused
135 // scroll if we need to and update our page control
138 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
140 CGUIControl *control = *it;
141 if (!control->IsVisible())
143 if (control->HasID(message.GetControlId()))
145 // find out whether this is the first or last control
146 if (IsFirstFocusableControl(control))
148 else if (IsLastFocusableControl(control))
149 ScrollTo(m_totalSize - Size());
150 else if (offset < m_scroller.GetValue())
152 else if (offset + Size(control) > m_scroller.GetValue() + Size())
153 ScrollTo(offset + Size(control) - Size());
156 offset += Size(control) + m_itemGap;
160 case GUI_MSG_SETFOCUS:
162 // we've been asked to focus. We focus the last control if it's on this page,
163 // else we'll focus the first focusable control from our offset (after verifying it)
165 // now check the focusControl's offset
167 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
169 CGUIControl *control = *it;
170 if (!control->IsVisible())
172 if (control->HasID(m_focusedControl))
174 if (offset >= m_scroller.GetValue() && offset + Size(control) <= m_scroller.GetValue() + Size())
175 return CGUIControlGroup::OnMessage(message);
178 offset += Size(control) + m_itemGap;
180 // find the first control on this page
182 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
184 CGUIControl *control = *it;
185 if (!control->IsVisible())
187 if (control->CanFocus() && offset >= m_scroller.GetValue() && offset + Size(control) <= m_scroller.GetValue() + Size())
189 m_focusedControl = control->GetID();
192 offset += Size(control) + m_itemGap;
196 case GUI_MSG_PAGE_CHANGE:
198 if (message.GetSenderId() == m_pageControl)
199 { // it's from our page control
200 ScrollTo((float)message.GetParam1());
206 return CGUIControlGroup::OnMessage(message);
209 void CGUIControlGroupList::ValidateOffset()
211 // calculate how many items we have on this page
212 m_totalSize = GetTotalSize();
213 // check our m_offset range
214 if (m_scroller.GetValue() > m_totalSize - Size())
215 m_scroller.SetValue(m_totalSize - Size());
216 if (m_scroller.GetValue() < 0) m_scroller.SetValue(0);
219 void CGUIControlGroupList::AddControl(CGUIControl *control, int position /*= -1*/)
221 // NOTE: We override control navigation here, but we don't override the <onleft> etc. builtins
223 if (position < 0 || position > (int)m_children.size()) // add at the end
224 position = (int)m_children.size();
227 { // set the navigation of items so that they form a list
228 CGUIAction beforeAction = (m_orientation == VERTICAL) ? m_actionUp : m_actionLeft;
229 CGUIAction afterAction = (m_orientation == VERTICAL) ? m_actionDown : m_actionRight;
230 if (m_children.size())
232 // we're inserting at the given position, so grab the items above and below and alter
233 // their navigation accordingly
234 CGUIControl *before = NULL;
235 CGUIControl *after = NULL;
237 { // inserting at the beginning
238 after = m_children[0];
239 if (!afterAction.HasActionsMeetingCondition() || afterAction.GetNavigation() == GetID()) // we're wrapping around bottom->top, so we have to update the last item
240 before = m_children[m_children.size() - 1];
241 if (!beforeAction.HasActionsMeetingCondition() || beforeAction.GetNavigation() == GetID()) // we're wrapping around top->bottom
242 beforeAction = CGUIAction(m_children[m_children.size() - 1]->GetID());
243 afterAction = CGUIAction(after->GetID());
245 else if (position == (int)m_children.size())
246 { // inserting at the end
247 before = m_children[m_children.size() - 1];
248 if (!beforeAction.HasActionsMeetingCondition() || beforeAction.GetNavigation() == GetID()) // we're wrapping around top->bottom, so we have to update the first item
249 after = m_children[0];
250 if (!afterAction.HasActionsMeetingCondition() || afterAction.GetNavigation() == GetID()) // we're wrapping around bottom->top
251 afterAction = CGUIAction(m_children[0]->GetID());
252 beforeAction = CGUIAction(before->GetID());
255 { // inserting somewhere in the middle
256 before = m_children[position - 1];
257 after = m_children[position];
258 beforeAction = CGUIAction(before->GetID());
259 afterAction = CGUIAction(after->GetID());
261 if (m_orientation == VERTICAL)
263 if (before) // update the DOWN action to point to us
264 before->SetNavigationAction(ACTION_MOVE_DOWN, CGUIAction(control->GetID()));
265 if (after) // update the UP action to point to us
266 after->SetNavigationAction(ACTION_MOVE_UP, CGUIAction(control->GetID()));
270 if (before) // update the RIGHT action to point to us
271 before->SetNavigationAction(ACTION_MOVE_RIGHT, CGUIAction(control->GetID()));
272 if (after) // update the LEFT action to point to us
273 after->SetNavigationAction(ACTION_MOVE_LEFT, CGUIAction(control->GetID()));
276 // now the control's nav
277 // set navigation path on orientation axis
278 // and try to apply other nav actions from grouplist
279 // don't override them if child have already defined actions
280 if (m_orientation == VERTICAL)
282 control->SetNavigationAction(ACTION_MOVE_UP, beforeAction);
283 control->SetNavigationAction(ACTION_MOVE_DOWN, afterAction);
284 control->SetNavigationAction(ACTION_MOVE_LEFT, m_actionLeft, false);
285 control->SetNavigationAction(ACTION_MOVE_RIGHT, m_actionRight, false);
289 control->SetNavigationAction(ACTION_MOVE_LEFT, beforeAction);
290 control->SetNavigationAction(ACTION_MOVE_RIGHT, afterAction);
291 control->SetNavigationAction(ACTION_MOVE_UP, m_actionUp, false);
292 control->SetNavigationAction(ACTION_MOVE_DOWN, m_actionDown, false);
294 control->SetNavigationAction(ACTION_NAV_BACK, m_actionBack, false);
296 if (!m_useControlPositions)
297 control->SetPosition(0,0);
298 CGUIControlGroup::AddControl(control, position);
299 m_totalSize = GetTotalSize();
303 void CGUIControlGroupList::ClearAll()
306 CGUIControlGroup::ClearAll();
307 m_scroller.SetValue(0);
310 #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
312 float CGUIControlGroupList::GetWidth() const
314 if (m_orientation == HORIZONTAL)
315 return CLAMP(m_totalSize, m_minSize, m_width);
316 return CGUIControlGroup::GetWidth();
319 float CGUIControlGroupList::GetHeight() const
321 if (m_orientation == VERTICAL)
322 return CLAMP(m_totalSize, m_minSize, m_height);
323 return CGUIControlGroup::GetHeight();
326 void CGUIControlGroupList::SetMinSize(float minWidth, float minHeight)
328 if (m_orientation == VERTICAL)
329 m_minSize = minHeight;
331 m_minSize = minWidth;
334 float CGUIControlGroupList::Size(const CGUIControl *control) const
336 return (m_orientation == VERTICAL) ? control->GetYPosition() + control->GetHeight() : control->GetXPosition() + control->GetWidth();
339 inline float CGUIControlGroupList::Size() const
341 return (m_orientation == VERTICAL) ? m_height : m_width;
344 void CGUIControlGroupList::ScrollTo(float offset)
346 m_scroller.ScrollTo(offset);
347 if (m_scroller.IsScrolling())
351 EVENT_RESULT CGUIControlGroupList::SendMouseEvent(const CPoint &point, const CMouseEvent &event)
353 // transform our position into child coordinates
354 CPoint childPoint(point);
355 m_transform.InverseTransformPosition(childPoint.x, childPoint.y);
356 if (CGUIControl::CanFocus())
359 float alignOffset = GetAlignOffset();
360 for (ciControls i = m_children.begin(); i != m_children.end(); ++i)
362 CGUIControl *child = *i;
363 if (child->IsVisible())
365 if (pos + Size(child) > m_scroller.GetValue() && pos < m_scroller.GetValue() + Size())
367 float offsetX = m_orientation == VERTICAL ? m_posX : m_posX + alignOffset + pos - m_scroller.GetValue();
368 float offsetY = m_orientation == VERTICAL ? m_posY + alignOffset + pos - m_scroller.GetValue() : m_posY;
369 EVENT_RESULT ret = child->SendMouseEvent(childPoint - CPoint(offsetX, offsetY), event);
371 { // we've handled the action, and/or have focused an item
375 pos += Size(child) + m_itemGap;
378 // none of our children want the event, but we may want it.
380 if (HitTest(childPoint) && (ret = OnMouseEvent(childPoint, event)))
383 m_focusedControl = 0;
384 return EVENT_RESULT_UNHANDLED;
387 void CGUIControlGroupList::UnfocusFromPoint(const CPoint &point)
390 CPoint controlCoords(point);
391 m_transform.InverseTransformPosition(controlCoords.x, controlCoords.y);
392 float alignOffset = GetAlignOffset();
393 for (iControls it = m_children.begin(); it != m_children.end(); ++it)
395 CGUIControl *child = *it;
396 if (child->IsVisible())
398 if (pos + Size(child) > m_scroller.GetValue() && pos < m_scroller.GetValue() + Size())
400 CPoint offset = (m_orientation == VERTICAL) ? CPoint(m_posX, m_posY + alignOffset + pos - m_scroller.GetValue()) : CPoint(m_posX + alignOffset + pos - m_scroller.GetValue(), m_posY);
401 child->UnfocusFromPoint(controlCoords - offset);
403 pos += Size(child) + m_itemGap;
406 CGUIControl::UnfocusFromPoint(point);
409 bool CGUIControlGroupList::GetCondition(int condition, int data) const
413 case CONTAINER_HAS_NEXT:
414 return (m_totalSize >= Size() && m_scroller.GetValue() < m_totalSize - Size());
415 case CONTAINER_HAS_PREVIOUS:
416 return (m_scroller.GetValue() > 0);
422 bool CGUIControlGroupList::IsFirstFocusableControl(const CGUIControl *control) const
424 for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
426 CGUIControl *child = *it;
427 if (child->IsVisible() && child->CanFocus())
428 { // found first focusable
429 return child == control;
435 bool CGUIControlGroupList::IsLastFocusableControl(const CGUIControl *control) const
437 for (crControls it = m_children.rbegin(); it != m_children.rend(); ++it)
439 CGUIControl *child = *it;
440 if (child->IsVisible() && child->CanFocus())
441 { // found first focusable
442 return child == control;
448 float CGUIControlGroupList::GetAlignOffset() const
450 if (m_totalSize < Size())
452 if (m_alignment & XBFONT_RIGHT)
453 return Size() - m_totalSize;
454 if (m_alignment & XBFONT_CENTER_X)
455 return (Size() - m_totalSize)*0.5f;
460 EVENT_RESULT CGUIControlGroupList::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
462 if (event.m_id == ACTION_MOUSE_WHEEL_UP || event.m_id == ACTION_MOUSE_WHEEL_DOWN)
464 // find the current control and move to the next or previous
466 for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
468 CGUIControl *control = *it;
469 if (!control->IsVisible()) continue;
470 float nextOffset = offset + Size(control) + m_itemGap;
471 if (event.m_id == ACTION_MOUSE_WHEEL_DOWN && nextOffset > m_scroller.GetValue() && m_scroller.GetValue() < m_totalSize - Size()) // past our current offset
473 ScrollTo(nextOffset);
474 return EVENT_RESULT_HANDLED;
476 else if (event.m_id == ACTION_MOUSE_WHEEL_UP && nextOffset >= m_scroller.GetValue() && m_scroller.GetValue() > 0) // at least at our current offset
479 return EVENT_RESULT_HANDLED;
484 else if (event.m_id == ACTION_GESTURE_BEGIN)
485 { // grab exclusive access
486 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID());
487 SendWindowMessage(msg);
488 return EVENT_RESULT_HANDLED;
490 else if (event.m_id == ACTION_GESTURE_END)
491 { // release exclusive access
492 CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID());
493 SendWindowMessage(msg);
494 return EVENT_RESULT_HANDLED;
496 else if (event.m_id == ACTION_GESTURE_PAN)
497 { // do the drag and validate our offset (corrects for end of scroll)
498 m_scroller.SetValue(CLAMP(m_scroller.GetValue() - ((m_orientation == HORIZONTAL) ? event.m_offsetX : event.m_offsetY), 0, m_totalSize - Size()));
500 return EVENT_RESULT_HANDLED;
503 return EVENT_RESULT_UNHANDLED;
506 float CGUIControlGroupList::GetTotalSize() const
509 for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
511 CGUIControl *control = *it;
512 if (!control->IsVisible()) continue;
513 totalSize += Size(control) + m_itemGap;
515 if (totalSize > 0) totalSize -= m_itemGap;