1 /*
2  *  Copyright (C) 2005-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIEditControl.h"
10 
11 #include "GUIKeyboardFactory.h"
12 #include "GUIUserMessages.h"
13 #include "GUIWindowManager.h"
14 #include "LocalizeStrings.h"
15 #include "ServiceBroker.h"
16 #include "XBDateTime.h"
17 #include "dialogs/GUIDialogNumeric.h"
18 #include "input/Key.h"
19 #include "input/XBMC_vkeys.h"
20 #include "utils/CharsetConverter.h"
21 #include "utils/Color.h"
22 #include "utils/Digest.h"
23 #include "utils/Variant.h"
24 #include "windowing/WinSystem.h"
25 
26 using namespace KODI::GUILIB;
27 
28 using KODI::UTILITY::CDigest;
29 
30 const char* CGUIEditControl::smsLetters[10] = { " !@#$%^&*()[]{}<>/\\|0", ".,;:\'\"-+_=?`~1", "abc2ABC", "def3DEF", "ghi4GHI", "jkl5JKL", "mno6MNO", "pqrs7PQRS", "tuv8TUV", "wxyz9WXYZ" };
31 const unsigned int CGUIEditControl::smsDelay = 1000;
32 
33 #ifdef TARGET_WINDOWS
34 extern HWND g_hWnd;
35 #endif
36 
CGUIEditControl(int parentID,int controlID,float posX,float posY,float width,float height,const CTextureInfo & textureFocus,const CTextureInfo & textureNoFocus,const CLabelInfo & labelInfo,const std::string & text)37 CGUIEditControl::CGUIEditControl(int parentID, int controlID, float posX, float posY,
38                                  float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
39                                  const CLabelInfo& labelInfo, const std::string &text)
40     : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
41 {
42   DefaultConstructor();
43   SetLabel(text);
44 }
45 
DefaultConstructor()46 void CGUIEditControl::DefaultConstructor()
47 {
48   ControlType = GUICONTROL_EDIT;
49   m_textOffset = 0;
50   m_textWidth = GetWidth();
51   m_cursorPos = 0;
52   m_cursorBlink = 0;
53   m_inputHeading = g_localizeStrings.Get(16028);
54   m_inputType = INPUT_TYPE_TEXT;
55   m_smsLastKey = 0;
56   m_smsKeyIndex = 0;
57   m_label.SetAlign(m_label.GetLabelInfo().align & XBFONT_CENTER_Y); // left align
58   m_label2.GetLabelInfo().offsetX = 0;
59   m_isMD5 = false;
60   m_invalidInput = false;
61   m_inputValidator = NULL;
62   m_inputValidatorData = NULL;
63   m_editLength = 0;
64   m_editOffset = 0;
65 }
66 
CGUIEditControl(const CGUIButtonControl & button)67 CGUIEditControl::CGUIEditControl(const CGUIButtonControl &button)
68     : CGUIButtonControl(button)
69 {
70   DefaultConstructor();
71 }
72 
73 CGUIEditControl::~CGUIEditControl(void) = default;
74 
OnMessage(CGUIMessage & message)75 bool CGUIEditControl::OnMessage(CGUIMessage &message)
76 {
77   if (message.GetMessage() == GUI_MSG_SET_TYPE)
78   {
79     SetInputType((INPUT_TYPE)message.GetParam1(), message.GetParam2());
80     return true;
81   }
82   else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
83   {
84     message.SetLabel(GetLabel2());
85     return true;
86   }
87   else if (message.GetMessage() == GUI_MSG_SET_TEXT &&
88           ((message.GetControlId() <= 0 && HasFocus()) || (message.GetControlId() == GetID())))
89   {
90     SetLabel2(message.GetLabel());
91     UpdateText();
92   }
93   return CGUIButtonControl::OnMessage(message);
94 }
95 
OnAction(const CAction & action)96 bool CGUIEditControl::OnAction(const CAction &action)
97 {
98   ValidateCursor();
99 
100   if (m_inputType != INPUT_TYPE_READONLY)
101   {
102     if (action.GetID() == ACTION_BACKSPACE)
103     {
104       // backspace
105       if (m_cursorPos)
106       {
107         if (!ClearMD5())
108           m_text2.erase(--m_cursorPos, 1);
109         UpdateText();
110       }
111       return true;
112     }
113     else if (action.GetID() == ACTION_MOVE_LEFT ||
114              action.GetID() == ACTION_CURSOR_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              action.GetID() == ACTION_CURSOR_RIGHT)
125     {
126       if (m_cursorPos < m_text2.size())
127       {
128         m_cursorPos++;
129         UpdateText(false);
130         return true;
131       }
132     }
133     else if (action.GetID() == ACTION_PASTE)
134     {
135       ClearMD5();
136       OnPasteClipboard();
137       return true;
138     }
139     else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_UNICODE && m_edit.empty())
140     {
141       // input from the keyboard (vkey, not ascii)
142       unsigned char b = action.GetID() & 0xFF;
143       if (b == XBMCVK_HOME)
144       {
145         m_cursorPos = 0;
146         UpdateText(false);
147         return true;
148       }
149       else if (b == XBMCVK_END)
150       {
151         m_cursorPos = m_text2.length();
152         UpdateText(false);
153         return true;
154       }
155       if (b == XBMCVK_LEFT && m_cursorPos > 0)
156       {
157         m_cursorPos--;
158         UpdateText(false);
159         return true;
160       }
161       if (b == XBMCVK_RIGHT && m_cursorPos < m_text2.length())
162       {
163         m_cursorPos++;
164         UpdateText(false);
165         return true;
166       }
167       if (b == XBMCVK_DELETE)
168       {
169         if (m_cursorPos < m_text2.length())
170         {
171           if (!ClearMD5())
172             m_text2.erase(m_cursorPos, 1);
173           UpdateText();
174           return true;
175         }
176       }
177       if (b == XBMCVK_BACK)
178       {
179         if (m_cursorPos > 0)
180         {
181           if (!ClearMD5())
182             m_text2.erase(--m_cursorPos, 1);
183           UpdateText();
184         }
185         return true;
186       }
187       else if (b == XBMCVK_RETURN || b == XBMCVK_NUMPADENTER)
188       {
189         // enter - send click message, but otherwise ignore
190         SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
191         return true;
192       }
193       else if (b == XBMCVK_ESCAPE)
194       { // escape - fallthrough to default action
195         return CGUIButtonControl::OnAction(action);
196       }
197     }
198     else if (action.GetID() == KEY_UNICODE)
199     {
200       // input from the keyboard
201       int ch = action.GetUnicode();
202       // ignore non-printing characters
203       if ( !((0 <= ch && ch < 0x8) || (0xE <= ch && ch < 0x1B) || (0x1C <= ch && ch < 0x20)) )
204       {
205       switch (ch)
206       {
207       case 9:  // tab, ignore
208       case 11: // Non-printing character, ignore
209       case 12: // Non-printing character, ignore
210         break;
211       case 10:
212       case 13:
213         {
214           // enter - send click message, but otherwise ignore
215           SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
216           return true;
217         }
218       case 27:
219         { // escape - fallthrough to default action
220           return CGUIButtonControl::OnAction(action);
221         }
222       case 8:
223         {
224           // backspace
225           if (m_cursorPos)
226           {
227             if (!ClearMD5())
228               m_text2.erase(--m_cursorPos, 1);
229           }
230           break;
231         }
232       case 127:
233         { // delete
234           if (m_cursorPos < m_text2.length())
235           {
236             if (!ClearMD5())
237               m_text2.erase(m_cursorPos, 1);
238           }
239         break;
240         }
241       default:
242         {
243           ClearMD5();
244           m_edit.clear();
245           m_text2.insert(m_text2.begin() + m_cursorPos++, action.GetUnicode());
246           break;
247         }
248       }
249       UpdateText();
250       return true;
251       }
252     }
253     else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
254     { // input from the remote
255       ClearMD5();
256       m_edit.clear();
257       OnSMSCharacter(action.GetID() - REMOTE_0);
258       return true;
259     }
260     else if (action.GetID() == ACTION_INPUT_TEXT)
261     {
262       m_edit.clear();
263       std::wstring str;
264       g_charsetConverter.utf8ToW(action.GetText(), str, false);
265       m_text2.insert(m_cursorPos, str);
266       m_cursorPos += str.size();
267       UpdateText();
268       return true;
269     }
270   }
271   return CGUIButtonControl::OnAction(action);
272 }
273 
OnClick()274 void CGUIEditControl::OnClick()
275 {
276   // we received a click - it's not from the keyboard, so pop up the virtual keyboard, unless
277   // that is where we reside!
278   if (GetParentID() == WINDOW_DIALOG_KEYBOARD)
279     return;
280 
281   std::string utf8;
282   g_charsetConverter.wToUTF8(m_text2, utf8);
283   bool textChanged = false;
284   switch (m_inputType)
285   {
286     case INPUT_TYPE_READONLY:
287       textChanged = false;
288       break;
289     case INPUT_TYPE_NUMBER:
290       textChanged = CGUIDialogNumeric::ShowAndGetNumber(utf8, m_inputHeading);
291       break;
292     case INPUT_TYPE_SECONDS:
293       textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420));
294       break;
295     case INPUT_TYPE_TIME:
296     {
297       CDateTime dateTime;
298       dateTime.SetFromDBTime(utf8);
299       KODI::TIME::SystemTime time;
300       dateTime.GetAsSystemTime(time);
301       if (CGUIDialogNumeric::ShowAndGetTime(time, !m_inputHeading.empty() ? m_inputHeading : g_localizeStrings.Get(21420)))
302       {
303         dateTime = CDateTime(time);
304         utf8 = dateTime.GetAsLocalizedTime("", false);
305         textChanged = true;
306       }
307       break;
308     }
309     case INPUT_TYPE_DATE:
310     {
311       CDateTime dateTime;
312       dateTime.SetFromDBDate(utf8);
313       if (dateTime < CDateTime(2000,1, 1, 0, 0, 0))
314         dateTime = CDateTime(2000, 1, 1, 0, 0, 0);
315       KODI::TIME::SystemTime date;
316       dateTime.GetAsSystemTime(date);
317       if (CGUIDialogNumeric::ShowAndGetDate(date, !m_inputHeading.empty() ? m_inputHeading : g_localizeStrings.Get(21420)))
318       {
319         dateTime = CDateTime(date);
320         utf8 = dateTime.GetAsDBDate();
321         textChanged = true;
322       }
323       break;
324     }
325     case INPUT_TYPE_IPADDRESS:
326       textChanged = CGUIDialogNumeric::ShowAndGetIPAddress(utf8, m_inputHeading);
327       break;
328     case INPUT_TYPE_SEARCH:
329       textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, true);
330       break;
331     case INPUT_TYPE_FILTER:
332       textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, false);
333       break;
334     case INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW:
335       textChanged = CGUIDialogNumeric::ShowAndVerifyNewPassword(utf8);
336       break;
337     case INPUT_TYPE_PASSWORD_MD5:
338       utf8 = ""; //! @todo Ideally we'd send this to the keyboard and tell the keyboard we have this type of input
339       // fallthrough
340     case INPUT_TYPE_TEXT:
341     default:
342       textChanged = CGUIKeyboardFactory::ShowAndGetInput(utf8, m_inputHeading, true, m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5);
343       break;
344   }
345   if (textChanged)
346   {
347     ClearMD5();
348     m_edit.clear();
349     g_charsetConverter.utf8ToW(utf8, m_text2);
350     m_cursorPos = m_text2.size();
351     UpdateText();
352     m_cursorPos = m_text2.size();
353   }
354 }
355 
UpdateText(bool sendUpdate)356 void CGUIEditControl::UpdateText(bool sendUpdate)
357 {
358   m_smsTimer.Stop();
359   if (sendUpdate)
360   {
361     ValidateInput();
362 
363     SEND_CLICK_MESSAGE(GetID(), GetParentID(), 0);
364 
365     m_textChangeActions.ExecuteActions(GetID(), GetParentID());
366   }
367   SetInvalid();
368 }
369 
SetInputType(CGUIEditControl::INPUT_TYPE type,const CVariant & heading)370 void CGUIEditControl::SetInputType(CGUIEditControl::INPUT_TYPE type, const CVariant& heading)
371 {
372   m_inputType = type;
373   if (heading.isString())
374     m_inputHeading = heading.asString();
375   else if (heading.isInteger() && heading.asInteger())
376     m_inputHeading = g_localizeStrings.Get(static_cast<uint32_t>(heading.asInteger()));
377   //! @todo Verify the current input string?
378 }
379 
RecalcLabelPosition()380 void CGUIEditControl::RecalcLabelPosition()
381 {
382   // ensure that our cursor is within our width
383   ValidateCursor();
384 
385   std::wstring text = GetDisplayedText();
386   m_textWidth = m_label.CalcTextWidth(text + L'|');
387   float beforeCursorWidth = m_label.CalcTextWidth(text.substr(0, m_cursorPos));
388   float afterCursorWidth = m_label.CalcTextWidth(text.substr(0, m_cursorPos) + L'|');
389   float leftTextWidth = m_label.GetRenderRect().Width();
390   float maxTextWidth = m_label.GetMaxWidth();
391   if (leftTextWidth > 0)
392     maxTextWidth -= leftTextWidth + spaceWidth;
393 
394   // if skinner forgot to set height :p
395   if (m_height == 0 && m_label.GetLabelInfo().font)
396     m_height = m_label.GetLabelInfo().font->GetTextHeight(1);
397 
398   if (m_textWidth > maxTextWidth)
399   { // we render taking up the full width, so make sure our cursor position is
400     // within the render window
401     if (m_textOffset + afterCursorWidth > maxTextWidth)
402     {
403       // move the position to the left (outside of the viewport)
404       m_textOffset = maxTextWidth - afterCursorWidth;
405     }
406     else if (m_textOffset + beforeCursorWidth < 0) // offscreen to the left
407     {
408       // otherwise use original position
409       m_textOffset = -beforeCursorWidth;
410     }
411     else if (m_textOffset + m_textWidth < maxTextWidth)
412     { // we have more text than we're allowed, but we aren't filling all the space
413       m_textOffset = maxTextWidth - m_textWidth;
414     }
415   }
416   else
417     m_textOffset = 0;
418 }
419 
ProcessText(unsigned int currentTime)420 void CGUIEditControl::ProcessText(unsigned int currentTime)
421 {
422   if (m_smsTimer.IsRunning() && m_smsTimer.GetElapsedMilliseconds() > smsDelay)
423     UpdateText();
424 
425   if (m_bInvalidated)
426   {
427     m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
428     m_label.SetText(m_info.GetLabel(GetParentID()));
429     RecalcLabelPosition();
430   }
431 
432   bool changed = false;
433 
434   m_clipRect.x1 = m_label.GetRenderRect().x1;
435   m_clipRect.x2 = m_clipRect.x1 + m_label.GetMaxWidth();
436   m_clipRect.y1 = m_posY;
437   m_clipRect.y2 = m_posY + m_height;
438 
439   // start by rendering the normal text
440   float leftTextWidth = m_label.GetRenderRect().Width();
441   if (leftTextWidth > 0)
442   {
443     // render the text on the left
444     changed |= m_label.SetColor(GetTextColor());
445     changed |= m_label.Process(currentTime);
446 
447     m_clipRect.x1 += leftTextWidth + spaceWidth;
448   }
449 
450   if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
451   {
452     uint32_t align = m_label.GetLabelInfo().align & XBFONT_CENTER_Y; // start aligned left
453     if (m_label2.GetTextWidth() < m_clipRect.Width())
454     { // align text as our text fits
455       if (leftTextWidth > 0)
456       { // right align as we have 2 labels
457         align |= XBFONT_RIGHT;
458       }
459       else
460       { // align by whatever the skinner requests
461         align |= (m_label2.GetLabelInfo().align & 3);
462       }
463     }
464     changed |= m_label2.SetMaxRect(m_clipRect.x1 + m_textOffset, m_posY, m_clipRect.Width() - m_textOffset, m_height);
465 
466     std::wstring text = GetDisplayedText();
467     std::string hint = m_hintInfo.GetLabel(GetParentID());
468 
469     if (!HasFocus() && text.empty() && !hint.empty())
470     {
471       changed |= m_label2.SetText(hint);
472     }
473     else if ((HasFocus() || GetParentID() == WINDOW_DIALOG_KEYBOARD) &&
474              m_inputType != INPUT_TYPE_READONLY)
475     {
476       changed |= SetStyledText(text);
477     }
478     else
479       changed |= m_label2.SetTextW(text);
480 
481     changed |= m_label2.SetAlign(align);
482     changed |= m_label2.SetColor(GetTextColor());
483     changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_CLIP);
484     changed |= m_label2.Process(currentTime);
485     CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
486   }
487   if (changed)
488     MarkDirtyRegion();
489 }
490 
RenderText()491 void CGUIEditControl::RenderText()
492 {
493   m_label.Render();
494 
495   if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
496   {
497     m_label2.Render();
498     CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
499   }
500 }
501 
GetTextColor() const502 CGUILabel::COLOR CGUIEditControl::GetTextColor() const
503 {
504   CGUILabel::COLOR color = CGUIButtonControl::GetTextColor();
505   if (color != CGUILabel::COLOR_DISABLED && HasInvalidInput())
506     return CGUILabel::COLOR_INVALID;
507 
508   return color;
509 }
510 
SetHint(const GUIINFO::CGUIInfoLabel & hint)511 void CGUIEditControl::SetHint(const GUIINFO::CGUIInfoLabel& hint)
512 {
513   m_hintInfo = hint;
514 }
515 
GetDisplayedText() const516 std::wstring CGUIEditControl::GetDisplayedText() const
517 {
518   std::wstring text(m_text2);
519   if (m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
520   {
521     text.clear();
522     if (m_smsTimer.IsRunning())
523     { // using the remove to input, so display the last key input
524       text.append(m_cursorPos - 1, L'*');
525       text.append(1, m_text2[m_cursorPos - 1]);
526       text.append(m_text2.size() - m_cursorPos, L'*');
527     }
528     else
529       text.append(m_text2.size(), L'*');
530   }
531   else if (!m_edit.empty())
532     text.insert(m_editOffset, m_edit);
533   return text;
534 }
535 
SetStyledText(const std::wstring & text)536 bool CGUIEditControl::SetStyledText(const std::wstring &text)
537 {
538   vecText styled;
539   styled.reserve(text.size() + 1);
540 
541   std::vector<UTILS::Color> colors;
542   colors.push_back(m_label.GetLabelInfo().textColor);
543   colors.push_back(m_label.GetLabelInfo().disabledColor);
544   UTILS::Color select = m_label.GetLabelInfo().selectedColor;
545   if (!select)
546     select = 0xFFFF0000;
547   colors.push_back(select);
548   colors.push_back(0x00FFFFFF);
549 
550   unsigned int startHighlight = m_cursorPos;
551   unsigned int endHighlight   = m_cursorPos + m_edit.size();
552   unsigned int startSelection = m_cursorPos + m_editOffset;
553   unsigned int endSelection   = m_cursorPos + m_editOffset + m_editLength;
554 
555   for (unsigned int i = 0; i < text.size(); i++)
556   {
557     unsigned int ch = text[i];
558     if (m_editLength > 0 && startSelection <= i && i < endSelection)
559       ch |= (2 << 16); // highlight the letters we're playing with
560     else if (!m_edit.empty() && (i < startHighlight || i >= endHighlight))
561       ch |= (1 << 16); // dim the bits we're not editing
562     styled.push_back(ch);
563   }
564 
565   // show the cursor
566   unsigned int ch = L'|';
567   if ((++m_cursorBlink % 64) > 32)
568     ch |= (3 << 16);
569   styled.insert(styled.begin() + m_cursorPos, ch);
570 
571   return m_label2.SetStyledText(styled, colors);
572 }
573 
ValidateCursor()574 void CGUIEditControl::ValidateCursor()
575 {
576   if (m_cursorPos > m_text2.size())
577     m_cursorPos = m_text2.size();
578 }
579 
SetLabel(const std::string & text)580 void CGUIEditControl::SetLabel(const std::string &text)
581 {
582   CGUIButtonControl::SetLabel(text);
583   SetInvalid();
584 }
585 
SetLabel2(const std::string & text)586 void CGUIEditControl::SetLabel2(const std::string &text)
587 {
588   m_edit.clear();
589   std::wstring newText;
590   g_charsetConverter.utf8ToW(text, newText, false);
591   if (newText != m_text2)
592   {
593     m_isMD5 = (m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW);
594     m_text2 = newText;
595     m_cursorPos = m_text2.size();
596     ValidateInput();
597     SetInvalid();
598   }
599 }
600 
GetLabel2() const601 std::string CGUIEditControl::GetLabel2() const
602 {
603   std::string text;
604   g_charsetConverter.wToUTF8(m_text2, text);
605   if (m_inputType == INPUT_TYPE_PASSWORD_MD5 && !m_isMD5)
606     return CDigest::Calculate(CDigest::Type::MD5, text);
607   return text;
608 }
609 
ClearMD5()610 bool CGUIEditControl::ClearMD5()
611 {
612   if (!(m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW) || !m_isMD5)
613     return false;
614 
615   m_text2.clear();
616   m_cursorPos = 0;
617   if (m_inputType != INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
618     m_isMD5 = false;
619   return true;
620 }
621 
GetCursorPosition() const622 unsigned int CGUIEditControl::GetCursorPosition() const
623 {
624   return m_cursorPos;
625 }
626 
SetCursorPosition(unsigned int iPosition)627 void CGUIEditControl::SetCursorPosition(unsigned int iPosition)
628 {
629   m_cursorPos = iPosition;
630 }
631 
OnSMSCharacter(unsigned int key)632 void CGUIEditControl::OnSMSCharacter(unsigned int key)
633 {
634   assert(key < 10);
635   if (m_smsTimer.IsRunning())
636   {
637     // we're already entering an SMS character
638     if (key != m_smsLastKey || m_smsTimer.GetElapsedMilliseconds() > smsDelay)
639     { // a different key was clicked than last time, or we have timed out
640       m_smsLastKey = key;
641       m_smsKeyIndex = 0;
642     }
643     else
644     { // same key as last time within the appropriate time period
645       m_smsKeyIndex++;
646       if (m_cursorPos)
647         m_text2.erase(--m_cursorPos, 1);
648     }
649   }
650   else
651   { // key is pressed for the first time
652     m_smsLastKey = key;
653     m_smsKeyIndex = 0;
654   }
655 
656   m_smsKeyIndex = m_smsKeyIndex % strlen(smsLetters[key]);
657 
658   m_text2.insert(m_text2.begin() + m_cursorPos++, smsLetters[key][m_smsKeyIndex]);
659   UpdateText();
660   m_smsTimer.StartZero();
661 }
662 
OnPasteClipboard()663 void CGUIEditControl::OnPasteClipboard()
664 {
665   std::wstring unicode_text;
666   std::string utf8_text;
667 
668   // Get text from the clipboard
669   utf8_text = CServiceBroker::GetWinSystem()->GetClipboardText();
670   g_charsetConverter.utf8ToW(utf8_text, unicode_text);
671 
672   // Insert the pasted text at the current cursor position.
673   if (unicode_text.length() > 0)
674   {
675     std::wstring left_end = m_text2.substr(0, m_cursorPos);
676     std::wstring right_end = m_text2.substr(m_cursorPos);
677 
678     m_text2 = left_end;
679     m_text2.append(unicode_text);
680     m_text2.append(right_end);
681     m_cursorPos += unicode_text.length();
682     UpdateText();
683   }
684 }
685 
SetInputValidation(StringValidation::Validator inputValidator,void * data)686 void CGUIEditControl::SetInputValidation(StringValidation::Validator inputValidator, void *data /* = NULL */)
687 {
688   if (m_inputValidator == inputValidator)
689     return;
690 
691   m_inputValidator = inputValidator;
692   m_inputValidatorData = data;
693   // the input validator has changed, so re-validate the current data
694   ValidateInput();
695 }
696 
ValidateInput(const std::wstring & data) const697 bool CGUIEditControl::ValidateInput(const std::wstring &data) const
698 {
699   if (m_inputValidator == NULL)
700     return true;
701 
702   return m_inputValidator(GetLabel2(), m_inputValidatorData != NULL ? m_inputValidatorData : const_cast<void*>((const void*)this));
703 }
704 
ValidateInput()705 void CGUIEditControl::ValidateInput()
706 {
707   // validate the input
708   bool invalid = !ValidateInput(m_text2);
709   // nothing to do if still valid/invalid
710   if (invalid != m_invalidInput)
711   {
712     // the validity state has changed so we need to update the control
713     m_invalidInput = invalid;
714 
715     // let the window/dialog know that the validity has changed
716     CGUIMessage msg(GUI_MSG_VALIDITY_CHANGED, GetID(), GetID(), m_invalidInput ? 0 : 1);
717     SendWindowMessage(msg);
718 
719     SetInvalid();
720   }
721 }
722 
SetFocus(bool focus)723 void CGUIEditControl::SetFocus(bool focus)
724 {
725   m_smsTimer.Stop();
726   CGUIControl::SetFocus(focus);
727   SetInvalid();
728 }
729 
GetDescriptionByIndex(int index) const730 std::string CGUIEditControl::GetDescriptionByIndex(int index) const
731 {
732   if (index == 0)
733     return GetDescription();
734   else if(index == 1)
735     return GetLabel2();
736 
737   return "";
738 }
739