2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "GUIEditControl.h"
22 #include "GUIWindowManager.h"
23 #include "utils/CharsetConverter.h"
24 #include "GUIKeyboardFactory.h"
25 #include "dialogs/GUIDialogNumeric.h"
26 #include "input/XBMC_vkeys.h"
28 #include "LocalizeStrings.h"
29 #include "XBDateTime.h"
30 #include "windowing/WindowingFactory.h"
31 #include "utils/md5.h"
33 #if defined(TARGET_DARWIN)
34 #include "osx/CocoaInterface.h"
37 const char* CGUIEditControl::smsLetters[10] = { " !@#$%^&*()[]{}<>/\\|0", ".,;:\'\"-+_=?`~1", "abc2", "def3", "ghi4", "jkl5", "mno6", "pqrs7", "tuv8", "wxyz9" };
38 const unsigned int CGUIEditControl::smsDelay = 1000;
46 CGUIEditControl::CGUIEditControl(int parentID, int controlID, float posX, float posY,
47 float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
48 const CLabelInfo& labelInfo, const std::string &text)
49 : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
55 void CGUIEditControl::DefaultConstructor()
57 ControlType = GUICONTROL_EDIT;
59 m_textWidth = GetWidth();
63 m_inputType = INPUT_TYPE_TEXT;
66 m_label.SetAlign(m_label.GetLabelInfo().align & XBFONT_CENTER_Y); // left align
67 m_label2.GetLabelInfo().offsetX = 0;
69 m_invalidInput = false;
70 m_inputValidator = NULL;
71 m_inputValidatorData = NULL;
74 CGUIEditControl::CGUIEditControl(const CGUIButtonControl &button)
75 : CGUIButtonControl(button)
80 CGUIEditControl::~CGUIEditControl(void)
84 bool CGUIEditControl::OnMessage(CGUIMessage &message)
86 if (message.GetMessage() == GUI_MSG_SET_TYPE)
88 SetInputType((INPUT_TYPE)message.GetParam1(), (int)message.GetParam2());
91 else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
93 message.SetLabel(GetLabel2());
96 else if (message.GetMessage() == GUI_MSG_SETFOCUS ||
97 message.GetMessage() == GUI_MSG_LOSTFOCUS)
101 else if (message.GetMessage() == GUI_MSG_SET_TEXT &&
102 ((message.GetControlId() <= 0 && HasFocus()) || (message.GetControlId() == GetID())))
104 SetLabel2(message.GetLabel());
107 return CGUIButtonControl::OnMessage(message);
110 bool CGUIEditControl::OnAction(const CAction &action)
114 if (m_inputType != INPUT_TYPE_READONLY)
116 if (action.GetID() == ACTION_BACKSPACE)
122 m_text2.erase(--m_cursorPos, 1);
127 else if (action.GetID() == ACTION_MOVE_LEFT)
136 else if (action.GetID() == ACTION_MOVE_RIGHT)
138 if ((unsigned int) m_cursorPos < m_text2.size())
145 else if (action.GetID() == ACTION_PASTE)
151 else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_ASCII)
153 // input from the keyboard (vkey, not ascii)
154 BYTE b = action.GetID() & 0xFF;
155 if (b == XBMCVK_HOME)
161 else if (b == XBMCVK_END)
163 m_cursorPos = m_text2.length();
167 if (b == XBMCVK_LEFT && m_cursorPos > 0)
173 if (b == XBMCVK_RIGHT && m_cursorPos < m_text2.length())
179 if (b == XBMCVK_DELETE)
181 if (m_cursorPos < m_text2.length())
184 m_text2.erase(m_cursorPos, 1);
189 if (b == XBMCVK_BACK)
194 m_text2.erase(--m_cursorPos, 1);
200 else if (action.GetID() >= KEY_ASCII)
202 // input from the keyboard
203 switch (action.GetUnicode())
210 // enter - send click message, but otherwise ignore
211 SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
215 { // escape - fallthrough to default action
216 return CGUIButtonControl::OnAction(action);
224 m_text2.erase(--m_cursorPos, 1);
231 m_text2.insert(m_text2.begin() + m_cursorPos++, (WCHAR)action.GetUnicode());
238 else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
239 { // input from the remote
241 if (m_inputType == INPUT_TYPE_FILTER)
242 { // filtering - use single number presses
243 m_text2.insert(m_text2.begin() + m_cursorPos++, L'0' + (action.GetID() - REMOTE_0));
247 OnSMSCharacter(action.GetID() - REMOTE_0);
251 return CGUIButtonControl::OnAction(action);
254 void CGUIEditControl::OnClick()
256 // we received a click - it's not from the keyboard, so pop up the virtual keyboard, unless
257 // that is where we reside!
258 if (GetParentID() == WINDOW_DIALOG_KEYBOARD)
262 g_charsetConverter.wToUTF8(m_text2, utf8);
263 bool textChanged = false;
264 CStdString heading = g_localizeStrings.Get(m_inputHeading ? m_inputHeading : 16028);
267 case INPUT_TYPE_READONLY:
270 case INPUT_TYPE_NUMBER:
271 textChanged = CGUIDialogNumeric::ShowAndGetNumber(utf8, heading);
273 case INPUT_TYPE_SECONDS:
274 textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420));
276 case INPUT_TYPE_TIME:
279 dateTime.SetFromDBTime(utf8);
281 dateTime.GetAsSystemTime(time);
282 if (CGUIDialogNumeric::ShowAndGetTime(time, heading > 0 ? heading : g_localizeStrings.Get(21420)))
284 dateTime = CDateTime(time);
285 utf8 = dateTime.GetAsLocalizedTime("", false);
290 case INPUT_TYPE_DATE:
293 dateTime.SetFromDBDate(utf8);
294 if (dateTime < CDateTime(2000,1, 1, 0, 0, 0))
295 dateTime = CDateTime(2000, 1, 1, 0, 0, 0);
297 dateTime.GetAsSystemTime(date);
298 if (CGUIDialogNumeric::ShowAndGetDate(date, heading > 0 ? heading : g_localizeStrings.Get(21420)))
300 dateTime = CDateTime(date);
301 utf8 = dateTime.GetAsDBDate();
306 case INPUT_TYPE_IPADDRESS:
307 textChanged = CGUIDialogNumeric::ShowAndGetIPAddress(utf8, heading);
309 case INPUT_TYPE_SEARCH:
310 textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, true);
312 case INPUT_TYPE_FILTER:
313 textChanged = CGUIKeyboardFactory::ShowAndGetFilter(utf8, false);
315 case INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW:
316 textChanged = CGUIDialogNumeric::ShowAndVerifyNewPassword(utf8);
318 case INPUT_TYPE_PASSWORD_MD5:
319 utf8 = ""; // TODO: Ideally we'd send this to the keyboard and tell the keyboard we have this type of input
321 case INPUT_TYPE_TEXT:
323 textChanged = CGUIKeyboardFactory::ShowAndGetInput(utf8, heading, true, m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5);
329 g_charsetConverter.utf8ToW(utf8, m_text2);
330 m_cursorPos = m_text2.size();
332 m_cursorPos = m_text2.size();
336 void CGUIEditControl::UpdateText(bool sendUpdate)
343 SEND_CLICK_MESSAGE(GetID(), GetParentID(), 0);
345 m_textChangeActions.ExecuteActions(GetID(), GetParentID());
350 void CGUIEditControl::SetInputType(CGUIEditControl::INPUT_TYPE type, int heading)
353 m_inputHeading = heading;
354 // TODO: Verify the current input string?
357 void CGUIEditControl::RecalcLabelPosition()
359 // ensure that our cursor is within our width
362 CStdStringW text = GetDisplayedText();
363 m_textWidth = m_label.CalcTextWidth(text + L'|');
364 float beforeCursorWidth = m_label.CalcTextWidth(text.substr(0, m_cursorPos));
365 float afterCursorWidth = m_label.CalcTextWidth(text.substr(0, m_cursorPos) + L'|');
366 float leftTextWidth = m_label.GetRenderRect().Width();
367 float maxTextWidth = m_label.GetMaxWidth();
368 if (leftTextWidth > 0)
369 maxTextWidth -= leftTextWidth + spaceWidth;
371 // if skinner forgot to set height :p
372 if (m_height == 0 && m_label.GetLabelInfo().font)
373 m_height = m_label.GetLabelInfo().font->GetTextHeight(1);
375 if (m_textWidth > maxTextWidth)
376 { // we render taking up the full width, so make sure our cursor position is
377 // within the render window
378 if (m_textOffset + afterCursorWidth > maxTextWidth)
380 // move the position to the left (outside of the viewport)
381 m_textOffset = maxTextWidth - afterCursorWidth;
383 else if (m_textOffset + beforeCursorWidth < 0) // offscreen to the left
385 // otherwise use original position
386 m_textOffset = -beforeCursorWidth;
388 else if (m_textOffset + m_textWidth < maxTextWidth)
389 { // we have more text than we're allowed, but we aren't filling all the space
390 m_textOffset = maxTextWidth - m_textWidth;
397 void CGUIEditControl::ProcessText(unsigned int currentTime)
399 if (m_smsTimer.GetElapsedMilliseconds() > smsDelay)
404 m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
405 m_label.SetText(m_info.GetLabel(GetParentID()));
406 RecalcLabelPosition();
409 bool changed = false;
411 m_clipRect.x1 = m_label.GetRenderRect().x1;
412 m_clipRect.x2 = m_clipRect.x1 + m_label.GetMaxWidth();
413 m_clipRect.y1 = m_posY;
414 m_clipRect.y2 = m_posY + m_height;
416 // start by rendering the normal text
417 float leftTextWidth = m_label.GetRenderRect().Width();
418 if (leftTextWidth > 0)
420 // render the text on the left
421 changed |= m_label.SetColor(GetTextColor());
422 changed |= m_label.Process(currentTime);
424 m_clipRect.x1 += leftTextWidth + spaceWidth;
427 if (g_graphicsContext.SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
429 uint32_t align = m_label.GetLabelInfo().align & XBFONT_CENTER_Y; // start aligned left
430 if (m_label2.GetTextWidth() < m_clipRect.Width())
431 { // align text as our text fits
432 if (leftTextWidth > 0)
433 { // right align as we have 2 labels
434 align |= XBFONT_RIGHT;
437 { // align by whatever the skinner requests
438 align |= (m_label2.GetLabelInfo().align & 3);
441 CStdStringW text = GetDisplayedText();
442 // add the cursor if we're focused
443 if (HasFocus() && m_inputType != INPUT_TYPE_READONLY)
446 if ((m_focusCounter % 64) > 32)
449 col = L"[COLOR 00FFFFFF]|[/COLOR]";
450 text.insert(m_cursorPos, col);
453 changed |= m_label2.SetMaxRect(m_clipRect.x1 + m_textOffset, m_posY, m_clipRect.Width() - m_textOffset, m_height);
455 changed |= m_label2.SetText(m_hintInfo.GetLabel(GetParentID()));
457 changed |= m_label2.SetTextW(text);
458 changed |= m_label2.SetAlign(align);
459 changed |= m_label2.SetColor(GetTextColor());
460 changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_CLIP);
461 changed |= m_label2.Process(currentTime);
462 g_graphicsContext.RestoreClipRegion();
468 void CGUIEditControl::RenderText()
472 if (g_graphicsContext.SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
475 g_graphicsContext.RestoreClipRegion();
479 CGUILabel::COLOR CGUIEditControl::GetTextColor() const
481 CGUILabel::COLOR color = CGUIButtonControl::GetTextColor();
482 if (color != CGUILabel::COLOR_DISABLED && HasInvalidInput())
483 return CGUILabel::COLOR_INVALID;
488 void CGUIEditControl::SetHint(const CGUIInfoLabel& hint)
493 CStdStringW CGUIEditControl::GetDisplayedText() const
495 if (m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
498 text.append(m_text2.size(), L'*');
504 void CGUIEditControl::ValidateCursor()
506 if (m_cursorPos > m_text2.size())
507 m_cursorPos = m_text2.size();
510 void CGUIEditControl::SetLabel(const std::string &text)
512 CGUIButtonControl::SetLabel(text);
516 void CGUIEditControl::SetLabel2(const std::string &text)
519 g_charsetConverter.utf8ToW(text, newText);
520 if (newText != m_text2)
522 m_isMD5 = (m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW);
524 m_cursorPos = m_text2.size();
530 CStdString CGUIEditControl::GetLabel2() const
533 g_charsetConverter.wToUTF8(m_text2, text);
534 if (m_inputType == INPUT_TYPE_PASSWORD_MD5 && !m_isMD5)
535 return XBMC::XBMC_MD5::GetMD5(text);
539 bool CGUIEditControl::ClearMD5()
541 if (!(m_inputType == INPUT_TYPE_PASSWORD_MD5 || m_inputType == INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW) || !m_isMD5)
546 if (m_inputType != INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW)
551 unsigned int CGUIEditControl::GetCursorPosition() const
556 void CGUIEditControl::SetCursorPosition(unsigned int iPosition)
558 m_cursorPos = iPosition;
561 void CGUIEditControl::OnSMSCharacter(unsigned int key)
564 bool sendUpdate = false;
565 if (m_smsTimer.IsRunning())
567 // we're already entering an SMS character
568 if (key != m_smsLastKey || m_smsTimer.GetElapsedMilliseconds() > smsDelay)
569 { // a different key was clicked than last time, or we have timed out
575 { // same key as last time within the appropriate time period
578 m_text2.erase(--m_cursorPos, 1);
582 { // key is pressed for the first time
587 m_smsKeyIndex = m_smsKeyIndex % strlen(smsLetters[key]);
589 m_text2.insert(m_text2.begin() + m_cursorPos++, smsLetters[key][m_smsKeyIndex]);
590 UpdateText(sendUpdate);
591 m_smsTimer.StartZero();
594 void CGUIEditControl::OnPasteClipboard()
596 CStdStringW unicode_text;
597 CStdStringA utf8_text;
599 // Get text from the clipboard
600 utf8_text = g_Windowing.GetClipboardText();
601 g_charsetConverter.utf8ToW(utf8_text, unicode_text);
603 // Insert the pasted text at the current cursor position.
604 if (unicode_text.length() > 0)
606 CStdStringW left_end = m_text2.substr(0, m_cursorPos);
607 CStdStringW right_end = m_text2.substr(m_cursorPos);
610 m_text2.append(unicode_text);
611 m_text2.append(right_end);
612 m_cursorPos += unicode_text.length();
617 void CGUIEditControl::SetInputValidation(StringValidation::Validator inputValidator, void *data /* = NULL */)
619 if (m_inputValidator == inputValidator)
622 m_inputValidator = inputValidator;
623 m_inputValidatorData = data;
624 // the input validator has changed, so re-validate the current data
628 bool CGUIEditControl::ValidateInput(const CStdStringW &data) const
630 if (m_inputValidator == NULL)
633 return m_inputValidator(GetLabel2(), (void*)(m_inputValidatorData != NULL ? m_inputValidatorData : this));
636 void CGUIEditControl::ValidateInput()
638 // validate the input
639 bool invalid = !ValidateInput(m_text2);
640 // nothing to do if still valid/invalid
641 if (invalid != m_invalidInput)
643 // the validity state has changed so we need to update the control
644 m_invalidInput = invalid;
646 // let the window/dialog know that the validity has changed
647 CGUIMessage msg(GUI_MSG_VALIDITY_CHANGED, GetID(), GetID(), m_invalidInput ? 0 : 1);
648 SendWindowMessage(msg);