Fix keymap.
[vuplus_xbmc] / xbmc / guilib / GUIControlGroupList.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 "GUIControlGroupList.h"
22 #include "Key.h"
23 #include "GUIInfoManager.h"
24 #include "GUIControlProfiler.h"
25 #include "GUIFont.h" // for XBFONT_* definitions
26
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)
30 {
31   m_itemGap = itemGap;
32   m_pageControl = pageControl;
33   m_totalSize = 0;
34   m_orientation = orientation;
35   m_alignment = alignment;
36   m_useControlPositions = useControlPositions;
37   ControlType = GUICONTROL_GROUPLIST;
38   m_minSize = 0;
39 }
40
41 CGUIControlGroupList::~CGUIControlGroupList(void)
42 {
43 }
44
45 void CGUIControlGroupList::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
46 {
47   if (m_scroller.Update(currentTime))
48     MarkDirtyRegion();
49
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)
53   {
54     CGUIControl *control = *it;
55     GUIPROFILER_VISIBILITY_BEGIN(control);
56     control->UpdateVisibility();
57     GUIPROFILER_VISIBILITY_END(control);
58   }
59
60   ValidateOffset();
61   if (m_pageControl)
62   {
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);
67   }
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)
71   {
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());
77     else
78       g_graphicsContext.SetOrigin(m_posX + pos - m_scroller.GetValue(), m_posY);
79     control->DoProcess(currentTime, dirtyregions);
80
81     if (control->IsVisible())
82       pos += Size(control) + m_itemGap;
83     g_graphicsContext.RestoreOrigin();
84   }
85   CGUIControl::Process(currentTime, dirtyregions);
86 }
87
88 void CGUIControlGroupList::Render()
89 {
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();
93   float focusedPos = 0;
94   CGUIControl *focusedControl = NULL;
95   for (iControls it = m_children.begin(); it != m_children.end(); ++it)
96   {
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())
101     {
102       focusedControl = control;
103       focusedPos = pos;
104     }
105     else
106     {
107       if (m_orientation == VERTICAL)
108         g_graphicsContext.SetOrigin(m_posX, m_posY + pos - m_scroller.GetValue());
109       else
110         g_graphicsContext.SetOrigin(m_posX + pos - m_scroller.GetValue(), m_posY);
111       control->DoRender();
112     }
113     if (control->IsVisible())
114       pos += Size(control) + m_itemGap;
115     g_graphicsContext.RestoreOrigin();
116   }
117   if (focusedControl)
118   {
119     if (m_orientation == VERTICAL)
120       g_graphicsContext.SetOrigin(m_posX, m_posY + focusedPos - m_scroller.GetValue());
121     else
122       g_graphicsContext.SetOrigin(m_posX + focusedPos - m_scroller.GetValue(), m_posY);
123     focusedControl->DoRender();
124   }
125   if (render) g_graphicsContext.RestoreClipRegion();
126   CGUIControl::Render();
127 }
128
129 bool CGUIControlGroupList::OnMessage(CGUIMessage& message)
130 {
131   switch (message.GetMessage() )
132   {
133   case GUI_MSG_FOCUSED:
134     { // a control has been focused
135       // scroll if we need to and update our page control
136       ValidateOffset();
137       float offset = 0;
138       for (iControls it = m_children.begin(); it != m_children.end(); ++it)
139       {
140         CGUIControl *control = *it;
141         if (!control->IsVisible())
142           continue;
143         if (control->HasID(message.GetControlId()))
144         {
145           // find out whether this is the first or last control
146           if (IsFirstFocusableControl(control))
147             ScrollTo(0);
148           else if (IsLastFocusableControl(control))
149             ScrollTo(m_totalSize - Size());
150           else if (offset < m_scroller.GetValue())
151             ScrollTo(offset);
152           else if (offset + Size(control) > m_scroller.GetValue() + Size())
153             ScrollTo(offset + Size(control) - Size());
154           break;
155         }
156         offset += Size(control) + m_itemGap;
157       }
158     }
159     break;
160   case GUI_MSG_SETFOCUS:
161     {
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)
164       ValidateOffset();
165       // now check the focusControl's offset
166       float offset = 0;
167       for (iControls it = m_children.begin(); it != m_children.end(); ++it)
168       {
169         CGUIControl *control = *it;
170         if (!control->IsVisible())
171           continue;
172         if (control->HasID(m_focusedControl))
173         {
174           if (offset >= m_scroller.GetValue() && offset + Size(control) <= m_scroller.GetValue() + Size())
175             return CGUIControlGroup::OnMessage(message);
176           break;
177         }
178         offset += Size(control) + m_itemGap;
179       }
180       // find the first control on this page
181       offset = 0;
182       for (iControls it = m_children.begin(); it != m_children.end(); ++it)
183       {
184         CGUIControl *control = *it;
185         if (!control->IsVisible())
186           continue;
187         if (control->CanFocus() && offset >= m_scroller.GetValue() && offset + Size(control) <= m_scroller.GetValue() + Size())
188         {
189           m_focusedControl = control->GetID();
190           break;
191         }
192         offset += Size(control) + m_itemGap;
193       }
194     }
195     break;
196   case GUI_MSG_PAGE_CHANGE:
197     {
198       if (message.GetSenderId() == m_pageControl)
199       { // it's from our page control
200         ScrollTo((float)message.GetParam1());
201         return true;
202       }
203     }
204     break;
205   }
206   return CGUIControlGroup::OnMessage(message);
207 }
208
209 void CGUIControlGroupList::ValidateOffset()
210 {
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);
217 }
218
219 void CGUIControlGroupList::AddControl(CGUIControl *control, int position /*= -1*/)
220 {
221   // NOTE: We override control navigation here, but we don't override the <onleft> etc. builtins
222   //       if specified.
223   if (position < 0 || position > (int)m_children.size()) // add at the end
224     position = (int)m_children.size();
225
226   if (control)
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())
231     {
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;
236       if (position == 0)
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());
244       }
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());
253       }
254       else
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());
260       }
261       if (m_orientation == VERTICAL)
262       {
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()));
267       }
268       else
269       {
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()));
274       }
275     }
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)
281     {
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);
286     }
287     else
288     {
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);
293     }
294     control->SetNavigationAction(ACTION_NAV_BACK, m_actionBack, false);
295
296     if (!m_useControlPositions)
297       control->SetPosition(0,0);
298     CGUIControlGroup::AddControl(control, position);
299     m_totalSize = GetTotalSize();
300   }
301 }
302
303 void CGUIControlGroupList::ClearAll()
304 {
305   m_totalSize = 0;
306   CGUIControlGroup::ClearAll();
307   m_scroller.SetValue(0);
308 }
309
310 #define CLAMP(x, low, high)  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
311
312 float CGUIControlGroupList::GetWidth() const
313 {
314   if (m_orientation == HORIZONTAL)
315     return CLAMP(m_totalSize, m_minSize, m_width);
316   return CGUIControlGroup::GetWidth();
317 }
318
319 float CGUIControlGroupList::GetHeight() const
320 {
321   if (m_orientation == VERTICAL)
322     return CLAMP(m_totalSize, m_minSize, m_height);
323   return CGUIControlGroup::GetHeight();
324 }
325
326 void CGUIControlGroupList::SetMinSize(float minWidth, float minHeight)
327 {
328   if (m_orientation == VERTICAL)
329     m_minSize = minHeight;
330   else
331     m_minSize = minWidth;
332 }
333
334 float CGUIControlGroupList::Size(const CGUIControl *control) const
335 {
336   return (m_orientation == VERTICAL) ? control->GetYPosition() + control->GetHeight() : control->GetXPosition() + control->GetWidth();
337 }
338
339 inline float CGUIControlGroupList::Size() const
340 {
341   return (m_orientation == VERTICAL) ? m_height : m_width;
342 }
343
344 void CGUIControlGroupList::ScrollTo(float offset)
345 {
346   m_scroller.ScrollTo(offset);
347   if (m_scroller.IsScrolling())
348     SetInvalid();
349 }
350
351 EVENT_RESULT CGUIControlGroupList::SendMouseEvent(const CPoint &point, const CMouseEvent &event)
352 {
353   // transform our position into child coordinates
354   CPoint childPoint(point);
355   m_transform.InverseTransformPosition(childPoint.x, childPoint.y);
356   if (CGUIControl::CanFocus())
357   {
358     float pos = 0;
359     float alignOffset = GetAlignOffset();
360     for (ciControls i = m_children.begin(); i != m_children.end(); ++i)
361     {
362       CGUIControl *child = *i;
363       if (child->IsVisible())
364       {
365         if (pos + Size(child) > m_scroller.GetValue() && pos < m_scroller.GetValue() + Size())
366         { // we're on screen
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);
370           if (ret)
371           { // we've handled the action, and/or have focused an item
372             return ret;
373           }
374         }
375         pos += Size(child) + m_itemGap;
376       }
377     }
378     // none of our children want the event, but we may want it.
379     EVENT_RESULT ret;
380     if (HitTest(childPoint) && (ret = OnMouseEvent(childPoint, event)))
381       return ret;
382   }
383   m_focusedControl = 0;
384   return EVENT_RESULT_UNHANDLED;
385 }
386
387 void CGUIControlGroupList::UnfocusFromPoint(const CPoint &point)
388 {
389   float pos = 0;
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)
394   {
395     CGUIControl *child = *it;
396     if (child->IsVisible())
397     {
398       if (pos + Size(child) > m_scroller.GetValue() && pos < m_scroller.GetValue() + Size())
399       { // we're on screen
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);
402       }
403       pos += Size(child) + m_itemGap;
404     }
405   }
406   CGUIControl::UnfocusFromPoint(point);
407 }
408
409 bool CGUIControlGroupList::GetCondition(int condition, int data) const
410 {
411   switch (condition)
412   {
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);
417   default:
418     return false;
419   }
420 }
421
422 bool CGUIControlGroupList::IsFirstFocusableControl(const CGUIControl *control) const
423 {
424   for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
425   {
426     CGUIControl *child = *it;
427     if (child->IsVisible() && child->CanFocus())
428     { // found first focusable
429       return child == control;
430     }
431   }
432   return false;
433 }
434
435 bool CGUIControlGroupList::IsLastFocusableControl(const CGUIControl *control) const
436 {
437   for (crControls it = m_children.rbegin(); it != m_children.rend(); ++it)
438   {
439     CGUIControl *child = *it;
440     if (child->IsVisible() && child->CanFocus())
441     { // found first focusable
442       return child == control;
443     }
444   }
445   return false;
446 }
447
448 float CGUIControlGroupList::GetAlignOffset() const
449 {
450   if (m_totalSize < Size())
451   {
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;
456   }
457   return 0.0f;
458 }
459
460 EVENT_RESULT CGUIControlGroupList::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
461 {
462   if (event.m_id == ACTION_MOUSE_WHEEL_UP || event.m_id == ACTION_MOUSE_WHEEL_DOWN)
463   {
464     // find the current control and move to the next or previous
465     float offset = 0;
466     for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
467     {
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
472       {
473         ScrollTo(nextOffset);
474         return EVENT_RESULT_HANDLED;
475       }
476       else if (event.m_id == ACTION_MOUSE_WHEEL_UP && nextOffset >= m_scroller.GetValue() && m_scroller.GetValue() > 0) // at least at our current offset
477       {
478         ScrollTo(offset);
479         return EVENT_RESULT_HANDLED;
480       }
481       offset = nextOffset;
482     }
483   }
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;
489   }
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;
495   }
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()));
499     SetInvalid();
500     return EVENT_RESULT_HANDLED;
501   }
502
503   return EVENT_RESULT_UNHANDLED;
504 }
505
506 float CGUIControlGroupList::GetTotalSize() const
507 {
508   float totalSize = 0;
509   for (ciControls it = m_children.begin(); it != m_children.end(); ++it)
510   {
511     CGUIControl *control = *it;
512     if (!control->IsVisible()) continue;
513     totalSize += Size(control) + m_itemGap;
514   }
515   if (totalSize > 0) totalSize -= m_itemGap;
516   return totalSize;
517 }