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