[osx/ios] remove unused function
[vuplus_xbmc] / xbmc / guilib / GUIEditControl.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 "GUIEditControl.h"
23 #include "GUIWindowManager.h"
24 #include "utils/CharsetConverter.h"
25 #include "dialogs/GUIDialogKeyboard.h"
26 #include "dialogs/GUIDialogNumeric.h"
27 #include "LocalizeStrings.h"
28 #include "DateTime.h"
29 #include "utils/md5.h"
30
31 #ifdef __APPLE__
32 #include "CocoaInterface.h"
33 #endif
34
35 const char* CGUIEditControl::smsLetters[10] = { " !@#$%^&*()[]{}<>/\\|0", ".,;:\'\"-+_=?`~1", "abc2", "def3", "ghi4", "jkl5", "mno6", "pqrs7", "tuv8", "wxyz9" };
36 const unsigned int CGUIEditControl::smsDelay = 1000;
37
38 using namespace std;
39
40 #ifdef WIN32
41 extern HWND g_hWnd;
42 #endif
43
44 CGUIEditControl::CGUIEditControl(int parentID, int controlID, float posX, float posY,
45                                  float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
46                                  const CLabelInfo& labelInfo, const std::string &text)
47     : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
48 {
49   DefaultConstructor();
50   SetLabel(text);
51 }
52
53 void CGUIEditControl::DefaultConstructor()
54 {
55   ControlType = GUICONTROL_EDIT;
56   m_textOffset = 0;
57   m_textWidth = GetWidth();
58   m_cursorPos = 0;
59   m_cursorBlink = 0;
60   m_inputHeading = 0;
61   m_inputType = INPUT_TYPE_TEXT;
62   m_smsLastKey = 0;
63   m_smsKeyIndex = 0;
64   m_label.SetAlign(m_label.GetLabelInfo().align & XBFONT_CENTER_Y); // left align
65   m_label2.GetLabelInfo().offsetX = 0;
66   m_isMD5 = false;
67 }
68
69 CGUIEditControl::CGUIEditControl(const CGUIButtonControl &button)
70     : CGUIButtonControl(button)
71 {
72   DefaultConstructor();
73 }
74
75 CGUIEditControl::~CGUIEditControl(void)
76 {
77 }
78
79 bool CGUIEditControl::OnMessage(CGUIMessage &message)
80 {
81   if (message.GetMessage() == GUI_MSG_SET_TYPE)
82   {
83     SetInputType((INPUT_TYPE)message.GetParam1(), (int)message.GetParam2());
84     return true;
85   }
86   else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
87   {
88     message.SetLabel(GetLabel2());
89     return true;
90   }
91   else if (message.GetMessage() == GUI_MSG_SETFOCUS ||
92            message.GetMessage() == GUI_MSG_LOSTFOCUS)
93   {
94     m_smsTimer.Stop();
95   }
96   return CGUIButtonControl::OnMessage(message);
97 }
98
99 bool CGUIEditControl::OnAction(const CAction &action)
100 {
101   ValidateCursor();
102
103   if (action.GetID() == ACTION_BACKSPACE)
104   {
105     // backspace
106     if (m_cursorPos)
107     {
108       if (!ClearMD5())
109         m_text2.erase(--m_cursorPos, 1);
110       UpdateText();
111     }
112     return true;
113   }
114   else if (action.GetID() == ACTION_MOVE_LEFT)
115   {
116     if (m_cursorPos > 0)
117     {
118       m_cursorPos--;
119       UpdateText(false);
120       return true;
121     }
122   }
123   else if (action.GetID() == ACTION_MOVE_RIGHT)
124   {
125     if ((unsigned int) m_cursorPos < m_text2.size())
126     {
127       m_cursorPos++;
128       UpdateText(false);
129       return true;
130     }
131   }
132   else if (action.GetID() == ACTION_PASTE)
133   {
134     ClearMD5();
135     OnPasteClipboard();
136   }
137   else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_ASCII)
138   {
139     // input from the keyboard (vkey, not ascii)
140     BYTE b = action.GetID() & 0xFF;
141     if (b == 0x24) // home
142     {
143       m_cursorPos = 0;
144       UpdateText(false);
145       return true;
146     }
147     else if (b == 0x23) // end
148     {
149       m_cursorPos = m_text2.length();
150       UpdateText(false);
151       return true;
152     }
153     if (b == 0x25 && m_cursorPos > 0)
154     { // left
155       m_cursorPos--;
156       UpdateText(false);
157       return true;
158     }
159     if (b == 0x27 && m_cursorPos < m_text2.length())
160     { // right
161       m_cursorPos++;
162       UpdateText(false);
163       return true;
164     }
165     if (b == 0x2e)
166     {
167       if (m_cursorPos < m_text2.length())
168       { // delete
169         if (!ClearMD5())
170           m_text2.erase(m_cursorPos, 1);
171         UpdateText();
172         return true;
173       }
174     }
175     if (b == 0x8)
176     {
177       if (m_cursorPos > 0)
178       { // backspace
179         if (!ClearMD5())
180           m_text2.erase(--m_cursorPos, 1);
181         UpdateText();
182       }
183       return true;
184     }
185   }
186   else if (action.GetID() >= KEY_ASCII)
187   {
188     // input from the keyboard
189     switch (action.GetUnicode())
190     {
191     case '\t':
192       break;
193     case 10:
194     case 13:
195       {
196         // enter - send click message, but otherwise ignore
197         SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
198         return true;
199       }
200     case 27:
201       { // escape - fallthrough to default action
202         return CGUIButtonControl::OnAction(action);
203       }
204     case 8:
205       {
206         // backspace
207         if (m_cursorPos)
208         {
209           if (!ClearMD5())
210             m_text2.erase(--m_cursorPos, 1);
211         }
212         break;
213       }
214     default:
215       {
216         ClearMD5();
217         m_text2.insert(m_text2.begin() + m_cursorPos++, (WCHAR)action.GetUnicode());
218         break;
219       }
220     }
221     UpdateText();
222     return true;
223   }
224   else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
225   { // input from the remote
226     ClearMD5();
227     if (m_inputType == INPUT_TYPE_FILTER)
228     { // filtering - use single number presses
229       m_text2.insert(m_text2.begin() + m_cursorPos++, L'0' + (action.GetID() - REMOTE_0));
230       UpdateText();
231     }
232     else
233       OnSMSCharacter(action.GetID() - REMOTE_0);
234     return true;
235   }
236   return CGUIButtonControl::OnAction(action);
237 }
238
239 void CGUIEditControl::OnClick()
240 {
241   // we received a click - it's not from the keyboard, so pop up the virtual keyboard, unless
242   // that is where we reside!
243   if (GetParentID() == WINDOW_DIALOG_KEYBOARD)
244     return;
245
246   CStdString utf8;
247   g_charsetConverter.wToUTF8(m_text2, utf8);
248   bool textChanged = false;
249   CStdString heading = g_localizeStrings.Get(m_inputHeading ? m_inputHeading : 16028);
250   switch (m_inputType)
251   {
252     case INPUT_TYPE_NUMBER:
253       textChanged = CGUIDialogNumeric::ShowAndGetNumber(utf8, heading);
254       break;
255     case INPUT_TYPE_SECONDS:
256       textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420));
257       break;
258     case INPUT_TYPE_DATE:
259     {
260       CDateTime dateTime;
261       dateTime.SetFromDBDate(utf8);
262       if (dateTime < CDateTime(2000,1, 1, 0, 0, 0))
263         dateTime = CDateTime(2000, 1, 1, 0, 0, 0);
264       SYSTEMTIME date;
265       dateTime.GetAsSystemTime(date);
266       if (CGUIDialogNumeric::ShowAndGetDate(date, g_localizeStrings.Get(21420)))
267       {
268         dateTime = CDateTime(date);
269         utf8 = dateTime.GetAsDBDate();
270         textChanged = true;
271       }
272       break;
273     }
274     case INPUT_TYPE_IPADDRESS:
275       textChanged = CGUIDialogNumeric::ShowAndGetIPAddress(utf8, heading);
276       break;
277     case INPUT_TYPE_SEARCH:
278       textChanged = CGUIDialogKeyboard::ShowAndGetFilter(utf8, true);
279       break;
280     case INPUT_TYPE_FILTER:
281       textChanged = CGUIDialogKeyboard::ShowAndGetFilter(utf8, false);
282       break;
283     case INPUT_TYPE_PASSWORD_MD5:
284       utf8 = ""; // TODO: Ideally we'd send this to the keyboard and tell the keyboard we have this type of input
285       // fallthrough
286     case INPUT_TYPE_TEXT:
287     default:
288       textChanged = CGUIDialogKeyboard::ShowAndGetInput(utf8, heading, true, m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5);
289       break;
290   }
291   if (textChanged)
292   {
293     ClearMD5();
294     g_charsetConverter.utf8ToW(utf8, m_text2);
295     m_cursorPos = m_text2.size();
296     UpdateText();
297     m_cursorPos = m_text2.size();
298   }
299 }
300
301 void CGUIEditControl::UpdateText(bool sendUpdate)
302 {
303   m_smsTimer.Stop();
304   if (sendUpdate)
305   {
306     SEND_CLICK_MESSAGE(GetID(), GetParentID(), 0);
307
308     vector<CGUIActionDescriptor> textChangeActions = m_textChangeActions;
309     for (unsigned int i = 0; i < textChangeActions.size(); i++)
310     {
311       CGUIMessage message(GUI_MSG_EXECUTE, GetID(), GetParentID());
312       message.SetAction(textChangeActions[i]);
313       g_windowManager.SendMessage(message);
314     }
315   }
316   SetInvalid();
317 }
318
319 void CGUIEditControl::SetInputType(CGUIEditControl::INPUT_TYPE type, int heading)
320 {
321   m_inputType = type;
322   m_inputHeading = heading;
323   // TODO: Verify the current input string?
324 }
325
326 void CGUIEditControl::RecalcLabelPosition()
327 {
328   // ensure that our cursor is within our width
329   ValidateCursor();
330
331   CStdStringW text = GetDisplayedText();
332   m_textWidth = m_label.CalcTextWidth(text + L'|');
333   float beforeCursorWidth = m_label.CalcTextWidth(text.Left(m_cursorPos));
334   float afterCursorWidth = m_label.CalcTextWidth(text.Left(m_cursorPos) + L'|');
335   float leftTextWidth = m_label.GetRenderRect().Width();
336   float maxTextWidth = m_label.GetMaxWidth();
337   if (leftTextWidth > 0)
338     maxTextWidth -= leftTextWidth + spaceWidth;
339
340   // if skinner forgot to set height :p
341   if (m_height == 0 && m_label.GetLabelInfo().font)
342     m_height = m_label.GetLabelInfo().font->GetTextHeight(1);
343
344   if (m_textWidth > maxTextWidth)
345   { // we render taking up the full width, so make sure our cursor position is
346     // within the render window
347     if (m_textOffset + afterCursorWidth > maxTextWidth)
348     {
349       // move the position to the left (outside of the viewport)
350       m_textOffset = maxTextWidth - afterCursorWidth;
351     }
352     else if (m_textOffset + beforeCursorWidth < 0) // offscreen to the left
353     {
354       // otherwise use original position
355       m_textOffset = -beforeCursorWidth;
356     }
357     else if (m_textOffset + m_textWidth < maxTextWidth)
358     { // we have more text than we're allowed, but we aren't filling all the space
359       m_textOffset = maxTextWidth - m_textWidth;
360     }
361   }
362   else
363     m_textOffset = 0;
364 }
365
366 void CGUIEditControl::RenderText()
367 {
368   if (m_smsTimer.GetElapsedMilliseconds() > smsDelay)
369     UpdateText();
370
371   if (m_bInvalidated)
372   {
373     m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
374     m_label.SetText(m_info.GetLabel(GetParentID()));
375     RecalcLabelPosition();
376   }
377
378
379   float posX = m_label.GetRenderRect().x1;
380   float maxTextWidth = m_label.GetMaxWidth();
381
382   // start by rendering the normal text
383   float leftTextWidth = m_label.GetRenderRect().Width();
384   if (leftTextWidth > 0)
385   {
386     // render the text on the left
387     m_label.SetColor(GetTextColor());
388     m_label.Render();
389     
390     posX += leftTextWidth + spaceWidth;
391     maxTextWidth -= leftTextWidth + spaceWidth;
392   }
393
394   if (g_graphicsContext.SetClipRegion(posX, m_posY, maxTextWidth, m_height))
395   {
396     uint32_t align = m_label.GetLabelInfo().align & XBFONT_CENTER_Y; // start aligned left
397     if (m_label2.GetTextWidth() < maxTextWidth)
398     { // align text as our text fits
399       if (leftTextWidth > 0)
400       { // right align as we have 2 labels
401         align |= XBFONT_RIGHT;
402       }
403       else
404       { // align by whatever the skinner requests
405         align |= (m_label2.GetLabelInfo().align & 3);
406       }
407     }
408     CStdStringW text = GetDisplayedText();
409     // add the cursor if we're focused
410     if (HasFocus())
411     {
412       CStdStringW col;
413       if ((m_focusCounter % 64) > 32)
414         col = L"|";
415       else
416         col = L"[COLOR 00FFFFFF]|[/COLOR]";
417       text.Insert(m_cursorPos, col);
418     }
419
420     m_label2.SetMaxRect(posX + m_textOffset, m_posY, maxTextWidth - m_textOffset, m_height);
421     m_label2.SetTextW(text);
422     m_label2.SetAlign(align);
423     m_label2.SetColor(GetTextColor());
424     m_label2.Render();
425     g_graphicsContext.RestoreClipRegion();
426   }
427 }
428
429 CStdStringW CGUIEditControl::GetDisplayedText() const
430 {
431   if (m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5)
432   {
433     CStdStringW text;
434     text.append(m_text2.size(), L'*');
435     return text;
436   }
437   return m_text2;
438 }
439
440 void CGUIEditControl::ValidateCursor()
441 {
442   if (m_cursorPos > m_text2.size())
443     m_cursorPos = m_text2.size();
444 }
445
446 void CGUIEditControl::SetLabel(const std::string &text)
447 {
448   CGUIButtonControl::SetLabel(text);
449   SetInvalid();
450 }
451
452 void CGUIEditControl::SetLabel2(const std::string &text)
453 {
454   CStdStringW newText;
455   g_charsetConverter.utf8ToW(text, newText);
456   if (newText != m_text2)
457   {
458     m_isMD5 = m_inputType == INPUT_TYPE_PASSWORD_MD5;
459     m_text2 = newText;
460     m_cursorPos = m_text2.size();
461     SetInvalid();
462   }
463 }
464
465 CStdString CGUIEditControl::GetLabel2() const
466 {
467   CStdString text;
468   g_charsetConverter.wToUTF8(m_text2, text);
469   if (m_inputType == INPUT_TYPE_PASSWORD_MD5 && !m_isMD5)
470     return XBMC::XBMC_MD5::GetMD5(text);
471   return text;
472 }
473
474 bool CGUIEditControl::ClearMD5()
475 {
476   if (m_inputType != INPUT_TYPE_PASSWORD_MD5 || !m_isMD5)
477     return false;
478   
479   m_text2.Empty();
480   m_cursorPos = 0;
481   m_isMD5 = false;
482   return true;
483 }
484
485 unsigned int CGUIEditControl::GetCursorPosition() const
486 {
487   return m_cursorPos;
488 }
489
490 void CGUIEditControl::SetCursorPosition(unsigned int iPosition)
491 {
492   m_cursorPos = iPosition;
493 }
494
495 void CGUIEditControl::OnSMSCharacter(unsigned int key)
496 {
497   assert(key < 10);
498   bool sendUpdate = false;
499   if (m_smsTimer.IsRunning())
500   {
501     // we're already entering an SMS character
502     if (key != m_smsLastKey || m_smsTimer.GetElapsedMilliseconds() > smsDelay)
503     { // a different key was clicked than last time, or we have timed out
504       m_smsLastKey = key;
505       m_smsKeyIndex = 0;
506       sendUpdate = true;
507     }
508     else
509     { // same key as last time within the appropriate time period
510       m_smsKeyIndex++;
511       if (m_cursorPos)
512         m_text2.erase(--m_cursorPos, 1);
513     }
514   }
515   else
516   { // key is pressed for the first time
517     m_smsLastKey = key;
518     m_smsKeyIndex = 0;
519   }
520
521   m_smsKeyIndex = m_smsKeyIndex % strlen(smsLetters[key]);
522
523   m_text2.insert(m_text2.begin() + m_cursorPos++, smsLetters[key][m_smsKeyIndex]);
524   UpdateText(sendUpdate);
525   m_smsTimer.StartZero();
526 }
527
528 void CGUIEditControl::OnPasteClipboard()
529 {
530 #if defined(__APPLE__) && !defined(__arm__)
531   const char *szStr = Cocoa_Paste();
532   if (szStr)
533   {
534     m_text2 += szStr;
535     m_cursorPos+=strlen(szStr);
536     UpdateText();
537   }
538 #elif defined _WIN32
539   if (OpenClipboard(g_hWnd))
540   {
541     HGLOBAL hglb = GetClipboardData(CF_TEXT);
542     if (hglb != NULL)
543     {
544       LPTSTR lptstr = (LPTSTR)GlobalLock(hglb);
545       if (lptstr != NULL)
546       {
547         m_text2 = (char*)lptstr;
548         GlobalUnlock(hglb);
549       }
550     }
551     CloseClipboard();
552     UpdateText();
553   }
554 #endif
555 }