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