[Test] Added tests for CUtil::SplitParams
[xbmc.git] / xbmc / guilib / GUIEditControl.cpp
blob6585f30b2d0262f28a52baf5a6b691d447b81b51
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIEditControl.h"
11 #include "GUIFont.h"
12 #include "GUIKeyboardFactory.h"
13 #include "GUIUserMessages.h"
14 #include "GUIWindowManager.h"
15 #include "LocalizeStrings.h"
16 #include "ServiceBroker.h"
17 #include "XBDateTime.h"
18 #include "dialogs/GUIDialogNumeric.h"
19 #include "input/actions/Action.h"
20 #include "input/actions/ActionIDs.h"
21 #include "input/keyboard/KeyIDs.h"
22 #include "input/keyboard/XBMC_vkeys.h"
23 #include "utils/CharsetConverter.h"
24 #include "utils/ColorUtils.h"
25 #include "utils/Digest.h"
26 #include "utils/Variant.h"
27 #include "utils/log.h"
28 #include "windowing/WinSystem.h"
30 #include <algorithm>
32 using namespace KODI::GUILIB;
34 using KODI::UTILITY::CDigest;
36 #ifdef TARGET_WINDOWS
37 extern HWND g_hWnd;
38 #endif
40 namespace
42 constexpr std::string_view smsLetters[] = {" !@#$%^&*()[]{}<>/\\|0",
43 ".,;:\'\"-+_=?`~1",
44 "abc2ABC",
45 "def3DEF",
46 "ghi4GHI",
47 "jkl5JKL",
48 "mno6MNO",
49 "pqrs7PQRS",
50 "tuv8TUV",
51 "wxyz9WXYZ"};
53 constexpr float smsDelay = 1000;
55 // Additional space between left label text and left label text in pixels
56 constexpr float TEXT_SPACE = 20.0f;
57 } // unnamed namespace
59 CGUIEditControl::CGUIEditControl(int parentID, int controlID, float posX, float posY,
60 float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
61 const CLabelInfo& labelInfo, const std::string &text)
62 : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
64 DefaultConstructor();
65 SetLabel(text);
67 // if skinner forgot to set height
68 if (m_height == 0 && m_label.GetLabelInfo().font)
70 m_height = m_label.GetLabelInfo().font->GetTextHeight(1);
71 CLog::LogF(LOGWARNING,
72 "No height has been set for GUI edit control ID {}, fallback to font height",
73 controlID);
77 void CGUIEditControl::DefaultConstructor()
79 ControlType = GUICONTROL_EDIT;
80 m_textOffset = 0;
81 m_cursorPos = 0;
82 m_cursorBlink = 0;
83 m_inputHeading = g_localizeStrings.Get(16028);
84 m_inputType = INPUT_TYPE_TEXT;
85 m_smsLastKey = 0;
86 m_smsKeyIndex = 0;
87 m_label2.GetLabelInfo().offsetX = 0;
88 m_label2.SetReversedTruncate(true); // Truncate the text by default on the left
89 m_isMD5 = false;
90 m_invalidInput = false;
91 m_inputValidator = NULL;
92 m_inputValidatorData = NULL;
93 m_editLength = 0;
94 m_editOffset = 0;
97 CGUIEditControl::CGUIEditControl(const CGUIButtonControl& button) : CGUIButtonControl(button)
99 DefaultConstructor();
102 CGUIEditControl::CGUIEditControl(const CGUIEditControl& button) : CGUIButtonControl(button)
104 DefaultConstructor();
107 CGUIEditControl::~CGUIEditControl(void) = default;
109 bool CGUIEditControl::OnMessage(CGUIMessage &message)
111 if (message.GetMessage() == GUI_MSG_SET_TYPE)
113 SetInputType((INPUT_TYPE)message.GetParam1(), message.GetParam2());
114 return true;
116 else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
118 message.SetLabel(GetLabel2());
119 return true;
121 else if (message.GetMessage() == GUI_MSG_SET_TEXT &&
122 ((message.GetControlId() <= 0 && HasFocus()) || (message.GetControlId() == GetID())))
124 SetLabel2(message.GetLabel());
125 UpdateText();
127 return CGUIButtonControl::OnMessage(message);
130 bool CGUIEditControl::OnAction(const CAction &action)
132 ValidateCursor();
134 if (m_inputType != INPUT_TYPE_READONLY)
136 if (action.GetID() == ACTION_BACKSPACE)
138 // backspace
139 if (m_cursorPos)
141 if (!ClearMD5())
142 m_text2.erase(--m_cursorPos, 1);
143 UpdateText();
145 return true;
147 else if (action.GetID() == ACTION_MOVE_LEFT ||
148 action.GetID() == ACTION_CURSOR_LEFT)
150 if (m_cursorPos > 0)
152 m_cursorPos--;
153 UpdateText(false);
154 return true;
157 else if (action.GetID() == ACTION_MOVE_RIGHT ||
158 action.GetID() == ACTION_CURSOR_RIGHT)
160 if (m_cursorPos < m_text2.size())
162 m_cursorPos++;
163 UpdateText(false);
164 return true;
167 else if (action.GetID() == ACTION_PASTE)
169 ClearMD5();
170 OnPasteClipboard();
171 return true;
173 else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_UNICODE && m_edit.empty())
175 // input from the keyboard (vkey, not ascii)
176 unsigned char b = action.GetID() & 0xFF;
177 if (b == XBMCVK_HOME)
179 m_cursorPos = 0;
180 UpdateText(false);
181 return true;
183 else if (b == XBMCVK_END)
185 m_cursorPos = m_text2.length();
186 UpdateText(false);
187 return true;
189 if (b == XBMCVK_LEFT && m_cursorPos > 0)
191 m_cursorPos--;
192 UpdateText(false);
193 return true;
195 if (b == XBMCVK_RIGHT && m_cursorPos < m_text2.length())
197 m_cursorPos++;
198 UpdateText(false);
199 return true;
201 if (b == XBMCVK_DELETE)
203 if (m_cursorPos < m_text2.length())
205 if (!ClearMD5())
206 m_text2.erase(m_cursorPos, 1);
207 UpdateText();
208 return true;
211 if (b == XBMCVK_BACK)
213 if (m_cursorPos > 0)
215 if (!ClearMD5())
216 m_text2.erase(--m_cursorPos, 1);
217 UpdateText();
219 return true;
221 else if (b == XBMCVK_RETURN || b == XBMCVK_NUMPADENTER)
223 // enter - send click message, but otherwise ignore
224 SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
225 return true;
227 else if (b == XBMCVK_ESCAPE)
228 { // escape - fallthrough to default action
229 return CGUIButtonControl::OnAction(action);
232 else if (action.GetID() == ACTION_KEYBOARD_COMPOSING_KEY)
234 ComposingCursorAppendChar(action.GetUnicode());
236 else if (action.GetID() == ACTION_KEYBOARD_COMPOSING_KEY_CANCELLED)
238 CancelKeyComposition(action.GetUnicode());
240 else if (action.GetID() == ACTION_KEYBOARD_COMPOSING_KEY_FINISHED)
242 ResetCursor();
244 else if (action.GetID() == KEY_UNICODE)
246 // input from the keyboard
247 int ch = action.GetUnicode();
248 // ignore non-printing characters
249 if ( !((0 <= ch && ch < 0x8) || (0xE <= ch && ch < 0x1B) || (0x1C <= ch && ch < 0x20)) )
251 switch (ch)
253 case 9: // tab, ignore
254 case 11: // Non-printing character, ignore
255 case 12: // Non-printing character, ignore
256 break;
257 case 10:
258 case 13:
260 // enter - send click message, but otherwise ignore
261 SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
262 return true;
264 case 27:
265 { // escape - fallthrough to default action
266 return CGUIButtonControl::OnAction(action);
268 case 8:
270 // backspace
271 if (m_cursorPos)
273 if (!ClearMD5())
274 m_text2.erase(--m_cursorPos, 1);
276 break;
278 case 127:
279 { // delete
280 if (m_cursorPos < m_text2.length())
282 if (!ClearMD5())
283 m_text2.erase(m_cursorPos, 1);
285 break;
287 default:
289 ClearMD5();
290 m_edit.clear();
291 m_text2.insert(m_text2.begin() + m_cursorPos++, action.GetUnicode());
292 break;
295 UpdateText();
296 return true;
299 else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
300 { // input from the remote
301 ClearMD5();
302 m_edit.clear();
303 OnSMSCharacter(action.GetID() - REMOTE_0);
304 return true;
306 else if (action.GetID() == ACTION_INPUT_TEXT)
308 m_edit.clear();
309 std::wstring str;
310 g_charsetConverter.utf8ToW(action.GetText(), str, false);
311 m_text2.insert(m_cursorPos, str);
312 m_cursorPos += str.size();
313 UpdateText();
314 return true;
317 return CGUIButtonControl::OnAction(action);
320 void CGUIEditControl::OnClick()
322 // we received a click - it's not from the keyboard, so pop up the virtual keyboard, unless
323 // that is where we reside!
324 if (GetParentID() == WINDOW_DIALOG_KEYBOARD)
325 return;
327 std::string utf8;
328 g_charsetConverter.wToUTF8(m_text2, utf8);
329 bool textChanged = false;
330 switch (m_inputType)
332 case INPUT_TYPE_READONLY:
333 textChanged = false;
334 break;
335 case INPUT_TYPE_NUMBER:
336 textChanged = CGUIDialogNumeric::ShowAndGetNumber(utf8, m_inputHeading);
337 break;
338 case INPUT_TYPE_SECONDS:
339 textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420));
340 break;
341 case INPUT_TYPE_TIME:
343 CDateTime dateTime;
344 dateTime.SetFromDBTime(utf8);
345 KODI::TIME::SystemTime time;
346 dateTime.GetAsSystemTime(time);
347 if (CGUIDialogNumeric::ShowAndGetTime(time, !m_inputHeading.empty() ? m_inputHeading : g_localizeStrings.Get(21420)))
349 dateTime = CDateTime(time);
350 utf8 = dateTime.GetAsLocalizedTime("", false);
351 textChanged = true;
353 break;
355 case INPUT_TYPE_DATE:
357 CDateTime dateTime;
358 dateTime.SetFromDBDate(utf8);
359 if (dateTime < CDateTime(2000,1, 1, 0, 0, 0))
360 dateTime = CDateTime(2000, 1, 1, 0, 0, 0);
361 KODI::TIME::SystemTime date;
362 dateTime.GetAsSystemTime(date);
363 if (CGUIDialogNumeric::ShowAndGetDate(date, !m_inputHeading.empty() ? m_inputHeading : g_localizeStrings.Get(21420)))
365 dateTime = CDateTime(date);
366 utf8 = dateTime.GetAsDBDate();
367 textChanged = true;
369 break;
371 case INPUT_TYPE_IPADDRESS:
372 textChanged = CGUIDialogNumeric::ShowAndGetIPAddress(utf8, m_inputHeading);
373 break;
374 case INPUT_TYPE_SEARCH:
375 textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, true);
376 break;
377 case INPUT_TYPE_FILTER:
378 textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, false);
379 break;
380 case INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW:
381 textChanged = CGUIDialogNumeric::ShowAndVerifyNewPassword(utf8);
382 break;
383 case INPUT_TYPE_PASSWORD_MD5:
384 utf8 = ""; //! @todo Ideally we'd send this to the keyboard and tell the keyboard we have this type of input
385 // fallthrough
386 [[fallthrough]];
387 case INPUT_TYPE_TEXT:
388 default:
389 textChanged = CGUIKeyboardFactory::ShowAndGetInput(utf8, m_inputHeading, true, m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5);
390 break;
392 if (textChanged)
394 ClearMD5();
395 m_edit.clear();
396 g_charsetConverter.utf8ToW(utf8, m_text2, false);
397 m_cursorPos = m_text2.size();
398 UpdateText();
399 m_cursorPos = m_text2.size();
403 void CGUIEditControl::UpdateText(bool sendUpdate)
405 m_smsTimer.Stop();
406 if (sendUpdate)
408 ValidateInput();
410 SEND_CLICK_MESSAGE(GetID(), GetParentID(), 0);
412 m_textChangeActions.ExecuteActions(GetID(), GetParentID());
414 SetInvalid();
417 void CGUIEditControl::SetInputType(CGUIEditControl::INPUT_TYPE type, const CVariant& heading)
419 m_inputType = type;
420 if (heading.isString())
421 m_inputHeading = heading.asString();
422 else if (heading.isInteger() && heading.asInteger())
423 m_inputHeading = g_localizeStrings.Get(static_cast<uint32_t>(heading.asInteger()));
424 //! @todo Verify the current input string?
427 void CGUIEditControl::RecalcRightLabelPosition()
429 // ensure that our cursor is within our width
430 ValidateCursor();
432 const std::wstring text = GetDisplayedText();
433 const float textWidth = m_label2.CalcTextWidth(text + L'|');
434 const float beforeCursorWidth = m_label2.CalcTextWidth(text.substr(0, m_cursorPos));
435 const float afterCursorWidth = m_label2.CalcTextWidth(text.substr(0, m_cursorPos) + L'|');
436 const float leftTextWidth = std::min(m_label.GetTextWidth(), m_label.GetMaxWidth());
437 float maxTextWidth = m_width - 2 * m_label.GetLabelInfo().offsetX;
439 if (leftTextWidth > 0)
440 maxTextWidth -= leftTextWidth + TEXT_SPACE;
442 if (textWidth > maxTextWidth)
443 { // we render taking up the full width, so make sure our cursor position is
444 // within the render window
445 if (m_textOffset + afterCursorWidth > maxTextWidth)
447 // move the position to the left (outside of the viewport)
448 m_textOffset = maxTextWidth - afterCursorWidth;
450 else if (m_textOffset + beforeCursorWidth < 0) // offscreen to the left
452 // otherwise use original position
453 m_textOffset = -beforeCursorWidth;
455 else if (m_textOffset + textWidth < maxTextWidth)
456 { // we have more text than we're allowed, but we aren't filling all the space
457 m_textOffset = maxTextWidth - textWidth;
460 else
461 m_textOffset = 0;
464 void CGUIEditControl::ProcessText(unsigned int currentTime)
466 if (m_smsTimer.IsRunning() && m_smsTimer.GetElapsedMilliseconds() > smsDelay)
467 UpdateText();
469 bool changed = false;
470 changed |= m_label.SetText(m_info.GetLabel(m_parentID));
472 m_clipRect.x1 = m_posX + m_label.GetLabelInfo().offsetX;
473 m_clipRect.x2 = m_clipRect.x1 + m_width - 2 * m_label.GetLabelInfo().offsetX;
474 m_clipRect.y1 = m_posY;
475 m_clipRect.y2 = m_posY + m_height;
477 // Limit left text max width to 50% of space when focused, otherwise 70%
478 const float maxTextWidth = m_width * (HasFocus() ? 0.5f : 0.7f);
480 const float leftTextWidth =
481 std::min(m_label.GetTextWidth(), maxTextWidth - 2 * m_label.GetLabelInfo().offsetX);
483 changed |= m_label.SetMaxRect(m_posX, m_posY, maxTextWidth, m_height);
485 if (m_bInvalidated)
487 if (!HasFocus() && leftTextWidth > 0)
488 m_textOffset = 0;
489 else
490 RecalcRightLabelPosition();
493 if (leftTextWidth > 0)
495 // render the text on the left
496 changed |= m_label.SetScrolling(HasFocus());
497 changed |= m_label.SetColor(GetTextColor());
498 changed |= m_label.Process(currentTime);
500 m_clipRect.x1 += leftTextWidth + TEXT_SPACE;
503 // render the text on the right
505 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
507 // set alignment for right label text
508 uint32_t align = m_label.GetLabelInfo().align & XBFONT_CENTER_Y; // start aligned left
509 if (leftTextWidth > 0)
510 { // right align as we have 2 labels
511 align |= XBFONT_RIGHT;
513 else
514 { // align by whatever the skinner requests
515 align |= (m_label2.GetLabelInfo().align & (XBFONT_RIGHT | XBFONT_CENTER_X));
518 changed |= m_label2.SetMaxRect(m_clipRect.x1 + m_textOffset, m_posY, m_clipRect.Width() - m_textOffset, m_height);
520 std::wstring text = GetDisplayedText();
521 std::string hint = m_hintInfo.GetLabel(GetParentID());
523 if (!HasFocus() && text.empty() && !hint.empty())
525 changed |= m_label2.SetText(hint);
527 else if ((HasFocus() || GetParentID() == WINDOW_DIALOG_KEYBOARD) &&
528 m_inputType != INPUT_TYPE_READONLY)
530 changed |= SetStyledText(text);
532 else
533 changed |= m_label2.SetTextW(text);
535 changed |= m_label2.SetAlign(align);
536 changed |= m_label2.SetColor(GetTextColor());
538 if (HasFocus() || leftTextWidth == 0)
539 changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_CLIP);
540 else
541 changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_TRUNCATE);
543 changed |= m_label2.Process(currentTime);
544 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
546 if (changed)
547 MarkDirtyRegion();
550 void CGUIEditControl::RenderText()
552 m_label.Render();
554 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
556 m_label2.Render();
557 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
561 CGUILabel::COLOR CGUIEditControl::GetTextColor() const
563 CGUILabel::COLOR color = CGUIButtonControl::GetTextColor();
564 if (color != CGUILabel::COLOR_DISABLED && HasInvalidInput())
565 return CGUILabel::COLOR_INVALID;
567 return color;
570 void CGUIEditControl::SetHint(const GUIINFO::CGUIInfoLabel& hint)
572 m_hintInfo = hint;
575 std::wstring CGUIEditControl::GetDisplayedText() const
577 std::wstring text(m_text2);
578 if (m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
580 text.clear();
581 if (m_smsTimer.IsRunning())
582 { // using the remove to input, so display the last key input
583 text.append(m_cursorPos - 1, L'*');
584 text.append(1, m_text2[m_cursorPos - 1]);
585 text.append(m_text2.size() - m_cursorPos, L'*');
587 else
588 text.append(m_text2.size(), L'*');
590 else if (!m_edit.empty())
591 text.insert(m_editOffset, m_edit);
592 return text;
595 bool CGUIEditControl::SetStyledText(const std::wstring &text)
597 vecText styled;
598 styled.reserve(text.size() + 1);
600 std::vector<UTILS::COLOR::Color> colors;
601 colors.push_back(m_label.GetLabelInfo().textColor);
602 colors.push_back(m_label.GetLabelInfo().disabledColor);
603 UTILS::COLOR::Color select = m_label.GetLabelInfo().selectedColor;
604 if (!select)
605 select = 0xFFFF0000;
606 colors.push_back(select);
607 colors.push_back(0x00FFFFFF);
609 unsigned int startHighlight = m_cursorPos;
610 unsigned int endHighlight = m_cursorPos + m_edit.size();
611 unsigned int startSelection = m_cursorPos + m_editOffset;
612 unsigned int endSelection = m_cursorPos + m_editOffset + m_editLength;
614 CGUIFont* font = m_label2.GetLabelInfo().font;
615 uint32_t style = (font ? font->GetStyle() : (FONT_STYLE_NORMAL & FONT_STYLE_MASK)) << 24;
617 for (unsigned int i = 0; i < text.size(); i++)
619 uint32_t ch = text[i] | style;
620 if (m_editLength > 0 && startSelection <= i && i < endSelection)
621 ch |= (2 << 16); // highlight the letters we're playing with
622 else if (!m_edit.empty() && (i < startHighlight || i >= endHighlight))
623 ch |= (1 << 16); // dim the bits we're not editing
624 styled.push_back(ch);
627 // show the cursor
628 unsigned int posChar = m_cursorPos;
629 for (const uint32_t& cursorChar : m_cursorChars)
631 uint32_t ch = cursorChar | style;
632 if (m_cursorBlinkEnabled)
634 if ((++m_cursorBlink % 64) > 32)
635 ch |= (3 << 16);
637 styled.insert(styled.begin() + posChar, ch);
638 posChar++;
640 return m_label2.SetStyledText(styled, colors);
643 void CGUIEditControl::ValidateCursor()
645 if (m_cursorPos > m_text2.size())
646 m_cursorPos = m_text2.size();
649 void CGUIEditControl::SetLabel(const std::string &text)
651 CGUIButtonControl::SetLabel(text);
652 SetInvalid();
655 void CGUIEditControl::SetLabel2(const std::string &text)
657 m_edit.clear();
658 std::wstring newText;
659 g_charsetConverter.utf8ToW(text, newText, false);
660 if (newText != m_text2)
662 m_isMD5 = (m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW);
663 m_text2 = newText;
664 m_cursorPos = m_text2.size();
665 ValidateInput();
666 SetInvalid();
670 std::string CGUIEditControl::GetLabel2() const
672 std::string text;
673 g_charsetConverter.wToUTF8(m_text2, text);
674 if (m_inputType == INPUT_TYPE_PASSWORD_MD5 && !m_isMD5)
675 return CDigest::Calculate(CDigest::Type::MD5, text);
676 return text;
679 bool CGUIEditControl::ClearMD5()
681 if (!(m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW) || !m_isMD5)
682 return false;
684 m_text2.clear();
685 m_cursorPos = 0;
686 if (m_inputType != INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
687 m_isMD5 = false;
688 return true;
691 unsigned int CGUIEditControl::GetCursorPosition() const
693 return m_cursorPos;
696 void CGUIEditControl::SetCursorPosition(unsigned int iPosition)
698 m_cursorPos = iPosition;
701 void CGUIEditControl::OnSMSCharacter(unsigned int key)
703 assert(key < 10);
704 if (m_smsTimer.IsRunning())
706 // we're already entering an SMS character
707 if (key != m_smsLastKey || m_smsTimer.GetElapsedMilliseconds() > smsDelay)
708 { // a different key was clicked than last time, or we have timed out
709 m_smsLastKey = key;
710 m_smsKeyIndex = 0;
712 else
713 { // same key as last time within the appropriate time period
714 m_smsKeyIndex++;
715 if (m_cursorPos)
716 m_text2.erase(--m_cursorPos, 1);
719 else
720 { // key is pressed for the first time
721 m_smsLastKey = key;
722 m_smsKeyIndex = 0;
725 m_smsKeyIndex = m_smsKeyIndex % smsLetters[key].size();
727 m_text2.insert(m_text2.begin() + m_cursorPos++, smsLetters[key][m_smsKeyIndex]);
728 UpdateText();
729 m_smsTimer.StartZero();
732 void CGUIEditControl::OnPasteClipboard()
734 std::wstring unicode_text;
735 std::string utf8_text;
737 // Get text from the clipboard
738 utf8_text = CServiceBroker::GetWinSystem()->GetClipboardText();
739 g_charsetConverter.utf8ToW(utf8_text, unicode_text, false);
741 // Insert the pasted text at the current cursor position.
742 if (unicode_text.length() > 0)
744 std::wstring left_end = m_text2.substr(0, m_cursorPos);
745 std::wstring right_end = m_text2.substr(m_cursorPos);
747 m_text2 = left_end;
748 m_text2.append(unicode_text);
749 m_text2.append(right_end);
750 m_cursorPos += unicode_text.length();
751 UpdateText();
755 void CGUIEditControl::SetInputValidation(StringValidation::Validator inputValidator, void *data /* = NULL */)
757 if (m_inputValidator == inputValidator)
758 return;
760 m_inputValidator = inputValidator;
761 m_inputValidatorData = data;
762 // the input validator has changed, so re-validate the current data
763 ValidateInput();
766 bool CGUIEditControl::ValidateInput(const std::wstring &data) const
768 if (m_inputValidator == NULL)
769 return true;
771 return m_inputValidator(GetLabel2(), m_inputValidatorData != NULL ? m_inputValidatorData : const_cast<void*>((const void*)this));
774 void CGUIEditControl::ValidateInput()
776 // validate the input
777 bool invalid = !ValidateInput(m_text2);
778 // nothing to do if still valid/invalid
779 if (invalid != m_invalidInput)
781 // the validity state has changed so we need to update the control
782 m_invalidInput = invalid;
784 // let the window/dialog know that the validity has changed
785 CGUIMessage msg(GUI_MSG_VALIDITY_CHANGED, GetID(), GetID(), m_invalidInput ? 0 : 1);
786 SendWindowMessage(msg);
788 SetInvalid();
792 void CGUIEditControl::SetFocus(bool focus)
794 m_smsTimer.Stop();
795 CGUIControl::SetFocus(focus);
796 SetInvalid();
799 std::string CGUIEditControl::GetDescriptionByIndex(int index) const
801 if (index == 0)
802 return GetDescription();
803 else if(index == 1)
804 return GetLabel2();
806 return "";
809 void CGUIEditControl::ComposingCursorAppendChar(std::uint32_t deadUnicodeKey)
811 std::uint32_t ch;
812 if (m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5 ||
813 m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
815 ch = '*';
817 else
819 ch = deadUnicodeKey;
822 if (IsComposingKey())
824 m_cursorChars.emplace_back(ch);
825 m_cursorCharsBuffer.emplace_back(deadUnicodeKey);
827 else
829 m_cursorChars = {ch};
830 m_cursorCharsBuffer.emplace_back(deadUnicodeKey);
832 m_cursorBlinkEnabled = false;
835 void CGUIEditControl::CancelKeyComposition(std::uint32_t deadUnicodeKey)
837 // sequence cancelled and reverted...
838 if (deadUnicodeKey == XBMCK_BACKSPACE)
840 ResetCursor();
842 // sequence cancelled and replay...
843 else
845 ClearMD5();
846 m_edit.clear();
847 for (const uint32_t& cursorChar : m_cursorCharsBuffer)
849 m_text2.insert(m_text2.begin() + m_cursorPos++, cursorChar);
851 UpdateText();
852 ResetCursor();
856 void CGUIEditControl::ResetCursor()
858 m_cursorChars = {'|'};
859 m_cursorCharsBuffer.clear();
860 m_cursorBlinkEnabled = true;
863 bool CGUIEditControl::IsComposingKey() const
865 return !m_cursorBlinkEnabled;