Fix keymap.
[vuplus_xbmc] / xbmc / guilib / GUIMultiSelectText.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 "GUIMultiSelectText.h"
22 #include "GUIWindowManager.h"
23 #include "Key.h"
24 #include "utils/log.h"
25 #include "utils/StringUtils.h"
26
27 using namespace std;
28
29 CGUIMultiSelectTextControl::CSelectableString::CSelectableString(CGUIFont *font, const CStdString &text, bool selectable, const CStdString &clickAction)
30  : m_text(font, false)
31 {
32   m_selectable = selectable;
33   m_clickAction = clickAction;
34   StringUtils::TrimLeft(m_clickAction, " =");
35   StringUtils::TrimRight(m_clickAction);
36   m_text.Update(text);
37   float height;
38   m_text.GetTextExtent(m_length, height);
39 }
40
41 CGUIMultiSelectTextControl::CGUIMultiSelectTextControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& textureFocus, const CTextureInfo& textureNoFocus, const CLabelInfo& labelInfo, const CGUIInfoLabel &content)
42     : CGUIControl(parentID, controlID, posX, posY, width, height)
43     , m_label(labelInfo)
44     , m_info(content)
45     , m_button(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
46 {
47   m_selectedItem = 0;
48   m_offset = 0;
49   m_totalWidth = 0;
50   m_scrollOffset = 0;
51   m_scrollSpeed = 0;
52   m_scrollLastTime = 0;
53   m_renderTime = 0;
54   m_label.align &= ~3; // we currently ignore all x alignment
55 }
56
57 CGUIMultiSelectTextControl::~CGUIMultiSelectTextControl(void)
58 {
59 }
60
61 bool CGUIMultiSelectTextControl::UpdateColors()
62 {
63   bool changed = CGUIControl::UpdateColors();
64   changed |= m_label.UpdateColors();
65
66   return changed;
67 }
68
69 void CGUIMultiSelectTextControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
70 {
71   m_renderTime = currentTime;
72
73   // check our selected item is in range
74   unsigned int numSelectable = GetNumSelectable();
75   if (!numSelectable)
76     SetFocus(false);
77   else if (m_selectedItem >= numSelectable)
78     m_selectedItem = numSelectable - 1;
79
80   // and validate our offset
81   if (m_offset + m_width > m_totalWidth)
82     m_offset = m_totalWidth - m_width;
83   if (m_offset < 0) m_offset = 0;
84
85   // handle scrolling
86   m_scrollOffset += m_scrollSpeed * (m_renderTime - m_scrollLastTime);
87   if ((m_scrollSpeed < 0 && m_scrollOffset < m_offset) ||
88       (m_scrollSpeed > 0 && m_scrollOffset > m_offset))
89   {
90     m_scrollOffset = m_offset;
91     m_scrollSpeed = 0;
92   }
93   m_scrollLastTime = m_renderTime;
94
95   g_graphicsContext.SetOrigin(-m_scrollOffset, 0);
96
97   // process the buttons
98   for (unsigned int i = 0; i < m_buttons.size(); i++)
99   {
100     m_buttons[i].SetFocus(HasFocus() && i == m_selectedItem);
101     m_buttons[i].DoProcess(currentTime, dirtyregions);
102   }
103
104   g_graphicsContext.RestoreOrigin();
105
106   CGUIControl::Process(currentTime, dirtyregions);
107 }
108
109 void CGUIMultiSelectTextControl::Render()
110 {
111   // clip and set our scrolling origin
112   bool clip(m_width < m_totalWidth);
113   if (clip)
114   { // need to crop
115     if (!g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height))
116       return; // nothing to render??
117   }
118   g_graphicsContext.SetOrigin(-m_scrollOffset, 0);
119
120   // render the buttons
121   for (unsigned int i = 0; i < m_buttons.size(); i++)
122     m_buttons[i].DoRender();
123
124   // position the text - we center vertically if applicable, and use the offsets.
125   // all x-alignment is ignored for now (see constructor)
126   float posX = m_posX;
127   float posY = m_posY + m_label.offsetY;
128   if (m_label.align & XBFONT_CENTER_Y)
129     posY = m_posY + m_height * 0.5f;
130
131   if (m_items.size() && m_items[0].m_selectable)
132     posX += m_label.offsetX;
133
134   // render the text
135   unsigned int num_selectable = 0;
136   for (unsigned int i = 0; i < m_items.size(); i++)
137   {
138     CSelectableString &string = m_items[i];
139     if (IsDisabled()) // all text is rendered with disabled color
140       string.m_text.Render(posX, posY, 0, m_label.disabledColor, m_label.shadowColor, m_label.align, 0, true);
141     else if (HasFocus() && string.m_selectable && num_selectable == m_selectedItem) // text is rendered with focusedcolor
142       string.m_text.Render(posX, posY, 0, m_label.focusedColor, m_label.shadowColor, m_label.align, 0);
143     else // text is rendered with textcolor
144       string.m_text.Render(posX, posY, 0, m_label.textColor, m_label.shadowColor, m_label.align, 0);
145     posX += string.m_length;
146     if (string.m_selectable)
147       num_selectable++;
148   }
149
150   g_graphicsContext.RestoreOrigin();
151   if (clip)
152     g_graphicsContext.RestoreClipRegion();
153
154   CGUIControl::Render();
155 }
156
157 void CGUIMultiSelectTextControl::UpdateInfo(const CGUIListItem *item)
158 {
159   if (m_info.IsEmpty())
160     return; // nothing to do
161
162   if (item)
163     UpdateText(m_info.GetItemLabel(item));
164   else
165     UpdateText(m_info.GetLabel(m_parentID));
166 }
167
168 bool CGUIMultiSelectTextControl::OnAction(const CAction &action)
169 {
170   if (action.GetID() == ACTION_SELECT_ITEM)
171   {
172     // item is clicked - see if we have a clickaction
173     CStdString clickAction;
174     unsigned int selected = 0;
175     for (unsigned int i = 0; i < m_items.size(); i++)
176     {
177       if (m_items[i].m_selectable)
178       {
179         if (m_selectedItem == selected)
180           clickAction = m_items[i].m_clickAction;
181         selected++;
182       }
183     }
184     if (!clickAction.empty())
185     { // have a click action -> perform it
186       CGUIMessage message(GUI_MSG_EXECUTE, m_controlID, m_parentID);
187       message.SetStringParam(clickAction);
188       g_windowManager.SendMessage(message);
189     }
190     else
191     { // no click action, just send a message to the window
192       CGUIMessage msg(GUI_MSG_CLICKED, m_controlID, m_parentID, m_selectedItem);
193       SendWindowMessage(msg);
194     }
195     return true;
196   }
197   return CGUIControl::OnAction(action);
198 }
199
200 void CGUIMultiSelectTextControl::OnLeft()
201 {
202   if (MoveLeft())
203     return;
204   CGUIControl::OnLeft();
205 }
206
207 void CGUIMultiSelectTextControl::OnRight()
208 {
209   if (MoveRight())
210     return;
211   CGUIControl::OnRight();
212 }
213
214 // movement functions (callable from lists)
215 bool CGUIMultiSelectTextControl::MoveLeft()
216 {
217   if (m_selectedItem > 0)
218     ScrollToItem(m_selectedItem - 1);
219   else if (GetNumSelectable() && m_actionLeft.GetNavigation() && m_actionLeft.GetNavigation() == m_controlID)
220     ScrollToItem(GetNumSelectable() - 1);
221   else
222     return false;
223   return true;
224 }
225
226 bool CGUIMultiSelectTextControl::MoveRight()
227 {
228   if (GetNumSelectable() && m_selectedItem < GetNumSelectable() - 1)
229     ScrollToItem(m_selectedItem + 1);
230   else if (m_actionRight.GetNavigation() && m_actionRight.GetNavigation() == m_controlID)
231     ScrollToItem(0);
232   else
233     return false;
234   return true;
235 }
236
237 void CGUIMultiSelectTextControl::SelectItemFromPoint(const CPoint &point)
238 {
239   int item = GetItemFromPoint(point);
240   if (item != -1)
241   {
242     ScrollToItem(item);
243     SetFocus(true);
244   }
245   else
246     SetFocus(false);
247 }
248
249 bool CGUIMultiSelectTextControl::HitTest(const CPoint &point) const
250 {
251   return (GetItemFromPoint(point) != -1);
252 }
253
254 bool CGUIMultiSelectTextControl::OnMouseOver(const CPoint &point)
255 {
256   ScrollToItem(GetItemFromPoint(point));
257   return CGUIControl::OnMouseOver(point);
258 }
259
260 EVENT_RESULT CGUIMultiSelectTextControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
261 {
262   if (event.m_id == ACTION_MOUSE_LEFT_CLICK)
263   {
264     m_selectedItem = GetItemFromPoint(point);
265     OnAction(CAction(ACTION_SELECT_ITEM));
266     return EVENT_RESULT_HANDLED;
267   }
268   return EVENT_RESULT_UNHANDLED;
269 }
270
271 int CGUIMultiSelectTextControl::GetItemFromPoint(const CPoint &point) const
272 {
273   if (!m_label.font) return -1;
274   float posX = m_posX;
275   unsigned int selectable = 0;
276   for (unsigned int i = 0; i < m_items.size(); i++)
277   {
278     const CSelectableString &string = m_items[i];
279     if (string.m_selectable)
280     {
281       CRect rect(posX, m_posY, posX + string.m_length, m_posY + m_height);
282       if (rect.PtInRect(point))
283         return selectable;
284       selectable++;
285     }
286     posX += string.m_length;
287   }
288   return -1;
289 }
290
291 void CGUIMultiSelectTextControl::UpdateText(const CStdString &text)
292 {
293   if (text == m_oldText)
294     return;
295
296   m_items.clear();
297
298   // parse our text into clickable blocks
299   // format is [ONCLICK <action>] [/ONCLICK]
300   size_t startClickable = text.find("[ONCLICK");
301   size_t startUnclickable = 0;
302
303   // add the first unclickable block
304   if (startClickable != CStdString::npos)
305     AddString(text.substr(startUnclickable, startClickable - startUnclickable), false);
306   else
307     AddString(text.substr(startUnclickable), false);
308   while (startClickable != CStdString::npos)
309   {
310     // grep out the action and the end of the string
311     size_t endAction = text.find(']', startClickable + 8);
312     size_t endClickable = text.find("[/ONCLICK]", startClickable + 8);
313     if (endAction != std::string::npos && endClickable != std::string::npos)
314     { // success - add the string, and move the start of our next unclickable portion along
315       AddString(text.substr(endAction + 1, endClickable - endAction - 1), true, text.substr(startClickable + 8, endAction - startClickable - 8));
316       startUnclickable = endClickable + 10;
317     }
318     else
319     {
320       CLog::Log(LOGERROR, "Invalid multiselect string %s", text.c_str());
321       break;
322     }
323     startClickable = text.find("[ONCLICK", startUnclickable);
324     // add the unclickable portion
325     if (startClickable != CStdString::npos)
326       AddString(text.substr(startUnclickable, startClickable - startUnclickable), false);
327     else
328       AddString(text.substr(startUnclickable), false);
329   }
330
331   m_oldText = text;
332
333   // finally, position our buttons
334   PositionButtons();
335 }
336
337 void CGUIMultiSelectTextControl::AddString(const CStdString &text, bool selectable, const CStdString &clickAction)
338 {
339   if (!text.empty())
340     m_items.push_back(CSelectableString(m_label.font, text, selectable, clickAction));
341 }
342
343 void CGUIMultiSelectTextControl::PositionButtons()
344 {
345   m_buttons.clear();
346
347   // add new buttons
348   m_totalWidth = 0;
349   if (m_items.size() && m_items.front().m_selectable)
350     m_totalWidth += m_label.offsetX;
351
352   for (unsigned int i = 0; i < m_items.size(); i++)
353   {
354     const CSelectableString &text = m_items[i];
355     if (text.m_selectable)
356     {
357       CGUIButtonControl button(m_button);
358       button.SetPosition(m_posX + m_totalWidth - m_label.offsetX, m_posY);
359       button.SetWidth(text.m_length + 2 * m_label.offsetX);
360       m_buttons.push_back(button);
361     }
362     m_totalWidth += text.m_length;
363   }
364
365   if (m_items.size() && m_items.back().m_selectable)
366     m_totalWidth += m_label.offsetX;
367 }
368
369 CStdString CGUIMultiSelectTextControl::GetDescription() const
370 {
371   // We currently just return the entire string - should we bother returning the
372   // particular subitems of this?
373   CStdString strLabel(m_info.GetLabel(m_parentID));
374   return strLabel;
375 }
376
377 unsigned int CGUIMultiSelectTextControl::GetNumSelectable() const
378 {
379   unsigned int selectable = 0;
380   for (unsigned int i = 0; i < m_items.size(); i++)
381     if (m_items[i].m_selectable)
382       selectable++;
383   return selectable;
384 }
385
386 unsigned int CGUIMultiSelectTextControl::GetFocusedItem() const
387 {
388   if (GetNumSelectable())
389     return m_selectedItem + 1;
390   return 0;
391 }
392
393 void CGUIMultiSelectTextControl::SetFocusedItem(unsigned int item)
394 {
395   SetFocus(item > 0);
396   if (item > 0)
397     ScrollToItem(item - 1);
398 }
399
400 bool CGUIMultiSelectTextControl::CanFocus() const
401 {
402   if (!GetNumSelectable()) return false;
403   return CGUIControl::CanFocus();
404 }
405
406 void CGUIMultiSelectTextControl::SetFocus(bool focus)
407 {
408   for (unsigned int i = 0; i < m_buttons.size(); i++)
409     m_buttons[i].SetFocus(focus);
410   CGUIControl::SetFocus(focus);
411 }
412
413 // overrides to allow anims to translate down to the focus image
414 void CGUIMultiSelectTextControl::SetAnimations(const vector<CAnimation> &animations)
415 {
416   // send any focus animations down to the focus image only
417   m_animations.clear();
418   vector<CAnimation> focusAnims;
419   for (unsigned int i = 0; i < animations.size(); i++)
420   {
421     const CAnimation &anim = animations[i];
422     if (anim.GetType() == ANIM_TYPE_FOCUS)
423       focusAnims.push_back(anim);
424     else
425       m_animations.push_back(anim);
426   }
427   m_button.SetAnimations(focusAnims);
428 }
429
430 void CGUIMultiSelectTextControl::ScrollToItem(unsigned int item)
431 {
432   static const unsigned int time_to_scroll = 200;
433   if (item >= m_buttons.size()) return;
434   // grab our button
435   const CGUIButtonControl &button = m_buttons[item];
436   float left = button.GetXPosition();
437   float right = left + button.GetWidth();
438   // make sure that we scroll so that this item is on screen
439   m_scrollOffset = m_offset;
440   if (left < m_posX + m_offset)
441     m_offset = left - m_posX;
442   else if (right > m_posX + m_offset + m_width)
443     m_offset = right - m_width - m_posX;
444   m_scrollSpeed = (m_offset - m_scrollOffset) / time_to_scroll;
445   m_selectedItem = item;
446 }
447