2 * Copyright (C) 2005-2008 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, write to
17 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 #include "GUIDialogKeyboard.h"
23 #include "settings/GUISettings.h"
24 #include "guilib/GUILabelControl.h"
25 #include "GUIDialogNumeric.h"
26 #include "GUIDialogOK.h"
27 #include "GUIUserMessages.h"
28 #include "guilib/GUIWindowManager.h"
29 #include "input/XBMC_vkeys.h"
30 #include "utils/RegExp.h"
31 #include "GUIPassword.h"
32 #include "utils/md5.h"
33 #include "utils/TimeUtils.h"
34 #include "Application.h"
35 #include "settings/AdvancedSettings.h"
36 #include "guilib/LocalizeStrings.h"
37 #include "interfaces/AnnouncementManager.h"
39 // Symbol mapping (based on MS virtual keyboard - may need improving)
40 static char symbol_map[37] = ")!@#$%^&*([]{}-_=+;:\'\",.<>/?\\|`~ ";
42 #define CTL_BUTTON_DONE 300
43 #define CTL_BUTTON_CANCEL 301
44 #define CTL_BUTTON_SHIFT 302
45 #define CTL_BUTTON_CAPS 303
46 #define CTL_BUTTON_SYMBOLS 304
47 #define CTL_BUTTON_LEFT 305
48 #define CTL_BUTTON_RIGHT 306
49 #define CTL_BUTTON_IP_ADDRESS 307
51 #define CTL_LABEL_EDIT 310
52 #define CTL_LABEL_HEADING 311
54 #define CTL_BUTTON_BACKSPACE 8
56 static char symbolButtons[] = "._-@/\\";
57 #define NUM_SYMBOLS sizeof(symbolButtons) - 1
59 #define SEARCH_DELAY 1000
60 #define REMOTE_SMS_DELAY 1000
62 CGUIDialogKeyboard::CGUIDialogKeyboard(void)
63 : CGUIDialog(WINDOW_DIALOG_KEYBOARD, "DialogKeyboard.xml")
65 m_bIsConfirmed = false;
67 m_hiddenInput = false;
68 m_filtering = FILTERING_NONE;
71 m_lastRemoteClickTime = 0;
74 CGUIDialogKeyboard::~CGUIDialogKeyboard(void)
77 void CGUIDialogKeyboard::OnInitWindow()
79 CGUIDialog::OnInitWindow();
81 m_bIsConfirmed = false;
83 // set alphabetic (capitals)
86 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
93 if (!m_strHeading.IsEmpty())
95 SET_CONTROL_LABEL(CTL_LABEL_HEADING, m_strHeading);
96 SET_CONTROL_VISIBLE(CTL_LABEL_HEADING);
100 SET_CONTROL_HIDDEN(CTL_LABEL_HEADING);
104 data["title"] = m_strHeading;
105 data["type"] = "keyboard";
106 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Input, "xbmc", "OnInputRequested", data);
109 bool CGUIDialogKeyboard::OnAction(const CAction &action)
112 if (action.GetID() == ACTION_BACKSPACE)
116 else if (action.GetID() == ACTION_ENTER)
120 else if (action.GetID() == ACTION_CURSOR_LEFT)
124 else if (action.GetID() == ACTION_CURSOR_RIGHT)
126 if ((unsigned int) GetCursorPos() == m_strEdit.size() && (m_strEdit.size() == 0 || m_strEdit[m_strEdit.size() - 1] != ' '))
133 else if (action.GetID() == ACTION_SHIFT)
137 else if (action.GetID() == ACTION_SYMBOLS)
141 else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
143 OnRemoteNumberClick(action.GetID());
145 else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_ASCII)
146 { // input from the keyboard (vkey, not ascii)
147 uint8_t b = action.GetID() & 0xFF;
148 if (b == XBMCVK_HOME)
150 MoveCursor(-GetCursorPos());
152 else if (b == XBMCVK_END)
154 MoveCursor(m_strEdit.GetLength() - GetCursorPos());
156 else if (b == XBMCVK_LEFT)
160 else if (b == XBMCVK_RIGHT)
164 else if (b == XBMCVK_RETURN || b == XBMCVK_NUMPADENTER)
168 else if (b == XBMCVK_DELETE)
170 if (GetCursorPos() < m_strEdit.GetLength())
176 else if (b == XBMCVK_BACK) Backspace();
177 else if (b == XBMCVK_ESCAPE) Close();
179 else if (action.GetID() >= KEY_ASCII)
180 { // input from the keyboard
181 //char ch = action.GetID() & 0xFF;
182 switch (action.GetUnicode())
194 default: //use character input
195 Character(action.GetUnicode());
199 else // unhandled by us - let's see if the baseclass wants it
200 handled = CGUIDialog::OnAction(action);
202 if (handled && m_filtering == FILTERING_SEARCH)
203 { // we did _something_, so make sure our search message filter is reset
209 bool CGUIDialogKeyboard::OnMessage(CGUIMessage& message)
211 CGUIDialog::OnMessage(message);
214 switch ( message.GetMessage() )
216 case GUI_MSG_CLICKED:
218 int iControl = message.GetSenderId();
222 case CTL_BUTTON_DONE:
227 case CTL_BUTTON_CANCEL:
230 case CTL_BUTTON_SHIFT:
233 case CTL_BUTTON_CAPS:
234 if (m_keyType == LOWER)
236 else if (m_keyType == CAPS)
240 case CTL_BUTTON_SYMBOLS:
243 case CTL_BUTTON_LEFT:
248 case CTL_BUTTON_RIGHT:
253 case CTL_BUTTON_IP_ADDRESS:
258 m_lastRemoteKeyClicked = 0;
259 OnClickButton(iControl);
265 case GUI_MSG_SET_TEXT:
266 SetText(message.GetLabel());
268 // close the dialog if requested
269 if (message.GetParam1() > 0)
277 void CGUIDialogKeyboard::SetText(const CStdString& aTextString)
280 g_charsetConverter.utf8ToW(aTextString, m_strEdit);
282 MoveCursor(m_strEdit.size());
285 CStdString CGUIDialogKeyboard::GetText() const
287 CStdString utf8String;
288 g_charsetConverter.wToUTF8(m_strEdit, utf8String);
292 void CGUIDialogKeyboard::Character(WCHAR ch)
295 // TODO: May have to make this routine take a WCHAR for the symbols?
296 m_strEdit.Insert(GetCursorPos(), ch);
301 void CGUIDialogKeyboard::FrameMove()
303 // reset the hide state of the label when the remote
304 // sms style input times out
305 if (m_lastRemoteClickTime && m_lastRemoteClickTime + REMOTE_SMS_DELAY < CTimeUtils::GetFrameTime())
307 // finished inputting a sms style character - turn off our shift and symbol states
308 ResetShiftAndSymbols();
310 CGUIDialog::FrameMove();
313 void CGUIDialogKeyboard::UpdateLabel() // FIXME seems to be called twice for one USB SDL keyboard action/character
315 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
318 CStdStringW edit = m_strEdit;
322 if (m_lastRemoteClickTime + REMOTE_SMS_DELAY > CTimeUtils::GetFrameTime() && m_strEdit.size())
323 { // using the remove to input, so display the last key input
324 edit.append(m_strEdit.size() - 1, L'*');
325 edit.append(1, m_strEdit[m_strEdit.size() - 1]);
328 edit.append(m_strEdit.size(), L'*');
330 // convert back to utf8
332 g_charsetConverter.wToUTF8(edit, utf8Edit);
333 pEdit->SetLabel(utf8Edit);
334 // Send off a search message
335 unsigned int now = CTimeUtils::GetFrameTime();
336 // don't send until the REMOTE_SMS_DELAY has passed
337 if (m_lastRemoteClickTime && m_lastRemoteClickTime + REMOTE_SMS_DELAY >= now)
339 if (m_filtering == FILTERING_CURRENT)
340 { // send our filter message
341 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS);
342 message.SetStringParam(utf8Edit);
343 g_windowManager.SendMessage(message);
346 if (m_filtering == FILTERING_SEARCH)
351 void CGUIDialogKeyboard::SendSearchMessage()
354 g_charsetConverter.wToUTF8(m_strEdit, utf8Edit);
355 // send our search message (only the active window needs it)
356 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_SEARCH_UPDATE);
357 message.SetStringParam(utf8Edit);
358 CGUIWindow *window = g_windowManager.GetWindow(g_windowManager.GetActiveWindow());
360 window->OnMessage(message);
363 void CGUIDialogKeyboard::Backspace()
365 int iPos = GetCursorPos();
368 m_strEdit.erase(iPos - 1, 1);
374 void CGUIDialogKeyboard::OnClickButton(int iButtonControl)
376 if (iButtonControl == CTL_BUTTON_BACKSPACE)
381 Character(GetCharacter(iButtonControl));
384 void CGUIDialogKeyboard::OnRemoteNumberClick(int key)
386 unsigned int now = CTimeUtils::GetFrameTime();
388 if (m_lastRemoteClickTime)
389 { // a remote key has been pressed
390 if (key != m_lastRemoteKeyClicked || m_lastRemoteClickTime + REMOTE_SMS_DELAY < now)
391 { // a different key was clicked than last time, or we have timed out
392 m_lastRemoteKeyClicked = key;
394 // reset our shift and symbol states, and update our label to ensure the search filter is sent
395 ResetShiftAndSymbols();
399 { // same key as last time within the appropriate time period
405 { // key is pressed for the first time
406 m_lastRemoteKeyClicked = key;
410 int arrayIndex = key - REMOTE_0;
411 m_indexInSeries = m_indexInSeries % strlen(s_charsSeries[arrayIndex]);
412 m_lastRemoteClickTime = now;
414 // Select the character that will be pressed
415 const char* characterPressed = s_charsSeries[arrayIndex];
416 characterPressed += m_indexInSeries;
418 // use caps where appropriate
419 char ch = *characterPressed;
420 bool caps = (m_keyType == CAPS && !m_bShift) || (m_keyType == LOWER && m_bShift);
421 if (!caps && *characterPressed >= 'A' && *characterPressed <= 'Z')
426 char CGUIDialogKeyboard::GetCharacter(int iButton)
429 if (iButton >= 48 && iButton <= 57)
431 if (m_keyType == SYMBOLS)
434 return symbol_map[iButton -48];
437 return (char)iButton;
439 else if (iButton == 32) // space
440 return (char)iButton;
441 else if (iButton >= 65 && iButton < 91)
443 if (m_keyType == SYMBOLS)
446 return symbol_map[iButton - 65 + 10];
448 if ((m_keyType == CAPS && m_bShift) || (m_keyType == LOWER && !m_bShift))
453 { // turn off the shift key
456 return (char) iButton;
459 { // check for symbols
460 for (unsigned int i = 0; i < NUM_SYMBOLS; i++)
461 if (iButton == symbolButtons[i])
462 return (char)iButton;
467 void CGUIDialogKeyboard::UpdateButtons()
470 { // show the button depressed
471 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CTL_BUTTON_SHIFT);
476 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CTL_BUTTON_SHIFT);
479 if (m_keyType == CAPS)
481 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CTL_BUTTON_CAPS);
486 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CTL_BUTTON_CAPS);
489 if (m_keyType == SYMBOLS)
491 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CTL_BUTTON_SYMBOLS);
496 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CTL_BUTTON_SYMBOLS);
502 CStdString aLabel = szLabel;
505 for (int iButton = 48; iButton <= 57; iButton++)
507 if (m_keyType == SYMBOLS)
508 aLabel[0] = symbol_map[iButton - 48];
511 SetControlLabel(iButton, aLabel);
514 // set correct alphabet characters...
516 for (int iButton = 65; iButton <= 90; iButton++)
518 // set the correct case...
519 if ((m_keyType == CAPS && m_bShift) || (m_keyType == LOWER && !m_bShift))
521 aLabel[0] = iButton + 32;
523 else if (m_keyType == SYMBOLS)
525 aLabel[0] = symbol_map[iButton - 65 + 10];
531 SetControlLabel(iButton, aLabel);
533 for (unsigned int i = 0; i < NUM_SYMBOLS; i++)
535 aLabel[0] = symbolButtons[i];
536 SetControlLabel(symbolButtons[i], aLabel);
540 // Show keyboard with initial value (aTextString) and replace with result string.
541 // Returns: true - successful display and input (empty result may return true or false depending on parameter)
542 // false - unsucessful display of the keyboard or cancelled editing
543 bool CGUIDialogKeyboard::ShowAndGetInput(CStdString& aTextString, const CVariant &heading, bool allowEmptyResult, bool hiddenInput /* = false */)
545 CGUIDialogKeyboard *pKeyboard = (CGUIDialogKeyboard*)g_windowManager.GetWindow(WINDOW_DIALOG_KEYBOARD);
551 pKeyboard->Initialize();
552 pKeyboard->SetHeading(heading);
553 pKeyboard->SetHiddenInput(hiddenInput);
554 pKeyboard->SetText(aTextString);
555 // do this using a thread message to avoid render() conflicts
556 ThreadMessage tMsg = {TMSG_DIALOG_DOMODAL, WINDOW_DIALOG_KEYBOARD, g_windowManager.GetActiveWindow()};
557 g_application.getApplicationMessenger().SendMessage(tMsg, true);
560 // If have text - update this.
561 if (pKeyboard->IsConfirmed())
563 aTextString = pKeyboard->GetText();
564 if (!allowEmptyResult && aTextString.IsEmpty())
571 bool CGUIDialogKeyboard::ShowAndGetInput(CStdString& aTextString, bool allowEmptyResult)
573 return ShowAndGetInput(aTextString, "", allowEmptyResult) != 0;
576 // Shows keyboard and prompts for a password.
577 // Differs from ShowAndVerifyNewPassword() in that no second verification is necessary.
578 bool CGUIDialogKeyboard::ShowAndGetNewPassword(CStdString& newPassword, const CVariant &heading, bool allowEmpty)
580 return ShowAndGetInput(newPassword, heading, allowEmpty, true);
583 // Shows keyboard and prompts for a password.
584 // Differs from ShowAndVerifyNewPassword() in that no second verification is necessary.
585 bool CGUIDialogKeyboard::ShowAndGetNewPassword(CStdString& newPassword)
587 return ShowAndGetNewPassword(newPassword, 12340, false);
590 // \brief Show keyboard twice to get and confirm a user-entered password string.
591 // \param newPassword Overwritten with user input if return=true.
592 // \param heading Heading to display
593 // \param allowEmpty Whether a blank password is valid or not.
594 // \return true if successful display and user input entry/re-entry. false if unsucessful display, no user input, or canceled editing.
595 bool CGUIDialogKeyboard::ShowAndVerifyNewPassword(CStdString& newPassword, const CVariant &heading, bool allowEmpty)
597 // Prompt user for password input
598 CStdString userInput = "";
599 if (!ShowAndGetInput(userInput, heading, allowEmpty, true))
600 { // user cancelled, or invalid input
603 // success - verify the password
604 CStdString checkInput = "";
605 if (!ShowAndGetInput(checkInput, 12341, allowEmpty, true))
606 { // user cancelled, or invalid input
609 // check the password
610 if (checkInput == userInput)
612 XBMC::XBMC_MD5 md5state;
613 md5state.append(userInput);
614 md5state.getDigest(newPassword);
615 newPassword.ToLower();
618 CGUIDialogOK::ShowAndGetInput(12341, 12344, 0, 0);
622 // \brief Show keyboard twice to get and confirm a user-entered password string.
623 // \param strNewPassword Overwritten with user input if return=true.
624 // \return true if successful display and user input entry/re-entry. false if unsucessful display, no user input, or canceled editing.
625 bool CGUIDialogKeyboard::ShowAndVerifyNewPassword(CStdString& newPassword)
627 CStdString heading = g_localizeStrings.Get(12340);
628 return ShowAndVerifyNewPassword(newPassword, heading, false);
631 // \brief Show keyboard and verify user input against strPassword.
632 // \param strPassword Value to compare against user input.
633 // \param dlgHeading String shown on dialog title. Converts to localized string if contains a positive integer.
634 // \param iRetries If greater than 0, shows "Incorrect password, %d retries left" on dialog line 2, else line 2 is blank.
635 // \return 0 if successful display and user input. 1 if unsucessful input. -1 if no user input or canceled editing.
636 int CGUIDialogKeyboard::ShowAndVerifyPassword(CStdString& strPassword, const CStdString& strHeading, int iRetries)
638 CStdString strHeadingTemp;
639 if (1 > iRetries && strHeading.size())
640 strHeadingTemp = strHeading;
642 strHeadingTemp.Format("%s - %i %s", g_localizeStrings.Get(12326).c_str(), g_guiSettings.GetInt("masterlock.maxretries") - iRetries, g_localizeStrings.Get(12343).c_str());
644 CStdString strUserInput = "";
645 if (!ShowAndGetInput(strUserInput, strHeadingTemp, false, true)) //bool hiddenInput = false/true ? TODO: GUI Setting to enable disable this feature y/n?
646 return -1; // user canceled out
648 if (!strPassword.IsEmpty())
650 if (strPassword == strUserInput)
653 CStdString md5pword2;
654 XBMC::XBMC_MD5 md5state;
655 md5state.append(strUserInput);
656 md5state.getDigest(md5pword2);
657 if (strPassword.Equals(md5pword2))
658 return 0; // user entered correct password
659 else return 1; // user must have entered an incorrect password
663 if (!strUserInput.IsEmpty())
665 XBMC::XBMC_MD5 md5state;
666 md5state.append(strUserInput);
667 md5state.getDigest(strPassword);
668 strPassword.ToLower();
669 return 0; // user entered correct password
675 void CGUIDialogKeyboard::OnDeinitWindow(int nextWindowID)
678 CGUIDialog::OnDeinitWindow(nextWindowID);
679 // reset the heading (we don't always have this)
682 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Input, "xbmc", "OnInputFinished");
685 void CGUIDialogKeyboard::MoveCursor(int iAmount)
687 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
690 pEdit->SetCursorPos(pEdit->GetCursorPos() + iAmount);
694 int CGUIDialogKeyboard::GetCursorPos() const
696 const CGUILabelControl* pEdit = (const CGUILabelControl*)GetControl(CTL_LABEL_EDIT);
699 return pEdit->GetCursorPos();
704 void CGUIDialogKeyboard::OnSymbols()
706 if (m_keyType == SYMBOLS)
713 void CGUIDialogKeyboard::OnShift()
715 m_bShift = !m_bShift;
719 void CGUIDialogKeyboard::OnIPAddress()
721 // find any IP address in the current string if there is any
722 // We match to #.#.#.#
723 CStdString utf8String;
724 g_charsetConverter.wToUTF8(m_strEdit, utf8String);
727 reg.RegComp("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+");
728 int start = reg.RegFind(utf8String.c_str());
732 length = reg.GetSubLength(0);
733 ip = utf8String.Mid(start, length);
736 start = utf8String.size();
737 if (CGUIDialogNumeric::ShowAndGetIPAddress(ip, g_localizeStrings.Get(14068)))
739 utf8String = utf8String.Left(start) + ip + utf8String.Mid(start + length);
740 g_charsetConverter.utf8ToW(utf8String, m_strEdit);
742 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
744 pEdit->SetCursorPos(m_strEdit.size());
748 void CGUIDialogKeyboard::ResetShiftAndSymbols()
750 if (m_bShift) OnShift();
751 if (m_keyType == SYMBOLS) OnSymbols();
752 m_lastRemoteClickTime = 0;
755 const char* CGUIDialogKeyboard::s_charsSeries[10] = { " 0!@#$%^&*()[]{}<>/\\|", ".,1;:\'\"-+_=?`~", "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
757 void CGUIDialogKeyboard::SetControlLabel(int id, const CStdString &label)
758 { // find all controls with this id, and set all their labels
759 CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), id);
760 message.SetLabel(label);
761 for (unsigned int i = 0; i < m_children.size(); i++)
763 if (m_children[i]->GetID() == id || m_children[i]->IsGroup())
764 m_children[i]->OnMessage(message);
768 void CGUIDialogKeyboard::OnOK()
770 m_bIsConfirmed = true;
774 bool CGUIDialogKeyboard::ShowAndGetFilter(CStdString &filter, bool searching)
776 CGUIDialogKeyboard *pKeyboard = (CGUIDialogKeyboard*)g_windowManager.GetWindow(WINDOW_DIALOG_KEYBOARD);
781 pKeyboard->m_filtering = searching ? FILTERING_SEARCH : FILTERING_CURRENT;
782 bool ret = ShowAndGetInput(filter, searching ? 16017 : 16028, true);
783 pKeyboard->m_filtering = FILTERING_NONE;
787 void CGUIDialogKeyboard::SetHeading(const CVariant &heading)
789 if (heading.isString())
790 m_strHeading = heading.asString();
791 else if (heading.isInteger() && heading.asInteger())
792 m_strHeading = g_localizeStrings.Get((uint32_t)heading.asInteger());