2 * Copyright (C) 2012-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 "interfaces/AnnouncementManager.h"
22 #include "input/XBMC_vkeys.h"
23 #include "guilib/GUILabelControl.h"
24 #include "guilib/GUIWindowManager.h"
25 #include "guilib/Key.h"
26 #include "guilib/LocalizeStrings.h"
27 #include "GUIUserMessages.h"
28 #include "GUIDialogNumeric.h"
29 #include "GUIDialogOK.h"
30 #include "GUIDialogKeyboardGeneric.h"
31 #include "utils/TimeUtils.h"
32 #include "utils/RegExp.h"
33 #include "ApplicationMessenger.h"
34 #include "windowing/WindowingFactory.h"
35 #include "utils/CharsetConverter.h"
37 #if defined(TARGET_DARWIN)
38 #include "osx/CocoaInterface.h"
41 // Symbol mapping (based on MS virtual keyboard - may need improving)
42 static char symbol_map[37] = ")!@#$%^&*([]{}-_=+;:\'\",.<>/?\\|`~ ";
44 #define CTL_BUTTON_DONE 300
45 #define CTL_BUTTON_CANCEL 301
46 #define CTL_BUTTON_SHIFT 302
47 #define CTL_BUTTON_CAPS 303
48 #define CTL_BUTTON_SYMBOLS 304
49 #define CTL_BUTTON_LEFT 305
50 #define CTL_BUTTON_RIGHT 306
51 #define CTL_BUTTON_IP_ADDRESS 307
52 #define CTL_BUTTON_CLEAR 308
54 #define CTL_LABEL_EDIT 310
55 #define CTL_LABEL_HEADING 311
57 #define CTL_BUTTON_BACKSPACE 8
59 static char symbolButtons[] = "._-@/\\";
60 #define NUM_SYMBOLS sizeof(symbolButtons) - 1
62 #define SEARCH_DELAY 1000
63 #define REMOTE_SMS_DELAY 1000
65 CGUIDialogKeyboardGeneric::CGUIDialogKeyboardGeneric()
66 : CGUIDialog(WINDOW_DIALOG_KEYBOARD, "DialogKeyboard.xml")
68 , m_pCharCallback(NULL)
70 m_bIsConfirmed = false;
72 m_hiddenInput = false;
77 m_lastRemoteClickTime = 0;
78 m_loadType = KEEP_IN_MEMORY;
81 void CGUIDialogKeyboardGeneric::OnInitWindow()
83 CGUIDialog::OnInitWindow();
85 m_bIsConfirmed = false;
87 // set alphabetic (capitals)
90 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
97 if (!m_strHeading.empty())
99 SET_CONTROL_LABEL(CTL_LABEL_HEADING, m_strHeading);
100 SET_CONTROL_VISIBLE(CTL_LABEL_HEADING);
104 SET_CONTROL_HIDDEN(CTL_LABEL_HEADING);
106 g_Windowing.EnableTextInput(true);
109 data["title"] = m_strHeading;
110 data["type"] = !m_hiddenInput ? "keyboard" : "password";
111 data["value"] = GetText();
112 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Input, "xbmc", "OnInputRequested", data);
115 bool CGUIDialogKeyboardGeneric::OnAction(const CAction &action)
118 if (action.GetID() == ACTION_BACKSPACE)
122 else if (action.GetID() == ACTION_ENTER)
126 else if (action.GetID() == ACTION_CURSOR_LEFT)
130 else if (action.GetID() == ACTION_CURSOR_RIGHT)
132 if (m_strEditing.IsEmpty() && (unsigned int) GetCursorPos() == m_strEdit.size() && (m_strEdit.size() == 0 || m_strEdit[m_strEdit.size() - 1] != ' '))
139 else if (action.GetID() == ACTION_SHIFT)
143 else if (action.GetID() == ACTION_SYMBOLS)
147 else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
149 OnRemoteNumberClick(action.GetID());
151 else if (action.GetID() == ACTION_PASTE)
155 else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_ASCII)
156 { // input from the keyboard (vkey, not ascii)
157 if (!m_strEditing.IsEmpty())
159 uint8_t b = action.GetID() & 0xFF;
160 if (b == XBMCVK_HOME)
164 else if (b == XBMCVK_END)
166 SetCursorPos(m_strEdit.GetLength());
168 else if (b == XBMCVK_LEFT)
172 else if (b == XBMCVK_RIGHT)
176 else if (b == XBMCVK_RETURN || b == XBMCVK_NUMPADENTER)
180 else if (b == XBMCVK_DELETE)
182 if (GetCursorPos() < m_strEdit.GetLength())
188 else if (b == XBMCVK_BACK) Backspace();
189 else if (b == XBMCVK_ESCAPE) Close();
191 else if (action.GetID() >= KEY_ASCII)
192 { // input from the keyboard
193 //char ch = action.GetID() & 0xFF;
194 int ch = action.GetUnicode();
196 // Ignore non-printing characters
197 if ( !((0 <= ch && ch < 0x8) || (0xE <= ch && ch < 0x1B) || (0x1C <= ch && ch < 0x20)) )
201 case 0x8: // backspace
204 case 0x9: // Tab (do nothing)
205 case 0xB: // Non-printing character, ignore
206 case 0xC: // Non-printing character, ignore
216 if (GetCursorPos() < m_strEdit.GetLength())
222 default: //use character input
223 // When we support text input method, we only accept text by gui text message.
224 if (!g_Windowing.IsTextInputEnabled())
225 Character(action.GetUnicode());
230 else // unhandled by us - let's see if the baseclass wants it
231 handled = CGUIDialog::OnAction(action);
233 if (handled && m_pCharCallback)
234 { // we did _something_, so make sure our search message filter is reset
235 m_pCharCallback(this, GetText());
240 bool CGUIDialogKeyboardGeneric::OnMessage(CGUIMessage& message)
242 CGUIDialog::OnMessage(message);
245 switch ( message.GetMessage() )
247 case GUI_MSG_CLICKED:
249 int iControl = message.GetSenderId();
253 case CTL_BUTTON_DONE:
256 case CTL_BUTTON_CANCEL:
259 case CTL_BUTTON_SHIFT:
262 case CTL_BUTTON_CAPS:
263 if (m_keyType == LOWER)
265 else if (m_keyType == CAPS)
269 case CTL_BUTTON_SYMBOLS:
272 case CTL_BUTTON_LEFT:
275 case CTL_BUTTON_RIGHT:
278 case CTL_BUTTON_IP_ADDRESS:
281 case CTL_BUTTON_CLEAR:
285 m_lastRemoteKeyClicked = 0;
286 OnClickButton(iControl);
292 case GUI_MSG_SET_TEXT:
293 SetText(message.GetLabel());
295 // close the dialog if requested
296 if (message.GetParam1() > 0)
300 case GUI_MSG_INPUT_TEXT:
301 InputText(message.GetLabel());
304 case GUI_MSG_INPUT_TEXT_EDIT:
305 InputTextEditing(message.GetLabel(), message.GetParam1(), message.GetParam2());
312 void CGUIDialogKeyboardGeneric::SetText(const CStdString& aTextString)
315 m_strEditing.clear();
316 m_iEditingOffset = 0;
317 g_charsetConverter.utf8ToW(aTextString, m_strEdit);
319 SetCursorPos(m_strEdit.size());
322 void CGUIDialogKeyboardGeneric::InputText(const CStdString& aTextString)
325 g_charsetConverter.utf8ToW(aTextString, newStr);
326 if (!newStr.IsEmpty())
328 m_strEditing.clear();
329 m_iEditingOffset = 0;
330 m_strEdit.Insert(GetCursorPos(), newStr);
332 MoveCursor(newStr.size());
336 void CGUIDialogKeyboardGeneric::InputTextEditing(const CStdString& aTextString, int start, int length)
338 m_strEditing.clear();
339 m_iEditingOffset = start;
340 m_iEditingLength = length;
341 g_charsetConverter.utf8ToW(aTextString, m_strEditing);
342 // CLog::Log(LOGDEBUG, "CGUIDialogKeyboardGeneric::InputTextEditing len %lu range(%d, %d) -> range len %d", m_strEditing.size(), m_iEditingOffset, length, m_iEditingLength);
344 SetCursorPos(GetCursorPos());
347 CStdString CGUIDialogKeyboardGeneric::GetText() const
349 CStdString utf8String;
350 g_charsetConverter.wToUTF8(m_strEdit, utf8String);
354 void CGUIDialogKeyboardGeneric::Character(WCHAR ch)
357 m_strEditing.clear();
358 m_iEditingOffset = 0;
359 // TODO: May have to make this routine take a WCHAR for the symbols?
360 m_strEdit.Insert(GetCursorPos(), ch);
365 void CGUIDialogKeyboardGeneric::FrameMove()
367 // reset the hide state of the label when the remote
368 // sms style input times out
369 if (m_lastRemoteClickTime && m_lastRemoteClickTime + REMOTE_SMS_DELAY < CTimeUtils::GetFrameTime())
371 // finished inputting a sms style character - turn off our shift and symbol states
372 ResetShiftAndSymbols();
374 CGUIDialog::FrameMove();
377 void CGUIDialogKeyboardGeneric::UpdateLabel() // FIXME seems to be called twice for one USB SDL keyboard action/character
379 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
382 CStdStringW edit = m_strEdit;
383 pEdit->SetHighlight(0, 0);
384 pEdit->SetSelection(0, 0);
388 if (m_lastRemoteClickTime + REMOTE_SMS_DELAY > CTimeUtils::GetFrameTime() && m_iCursorPos > 0)
389 { // using the remove to input, so display the last key input
390 edit.append(m_iCursorPos - 1, L'*');
391 edit.append(1, m_strEdit[m_iCursorPos - 1]);
394 edit.append(m_strEdit.size(), L'*');
396 else if (!m_strEditing.IsEmpty())
398 edit.Insert(m_iCursorPos, m_strEditing);
399 pEdit->SetHighlight(m_iCursorPos, m_iCursorPos + m_strEditing.size());
400 if (m_iEditingLength > 0)
401 pEdit->SetSelection(m_iCursorPos + m_iEditingOffset, m_iCursorPos + m_iEditingOffset + m_iEditingLength);
403 // convert back to utf8
405 g_charsetConverter.wToUTF8(edit, utf8Edit);
406 pEdit->SetLabel(utf8Edit);
407 // Send off a search message
408 unsigned int now = CTimeUtils::GetFrameTime();
409 // don't send until the REMOTE_SMS_DELAY has passed
410 if (m_lastRemoteClickTime && m_lastRemoteClickTime + REMOTE_SMS_DELAY >= now)
415 // do not send editing text comes from system input method
416 if (!m_hiddenInput && !m_strEditing.IsEmpty())
417 g_charsetConverter.wToUTF8(m_strEdit, utf8Edit);
418 m_pCharCallback(this, utf8Edit);
423 void CGUIDialogKeyboardGeneric::Backspace()
425 int iPos = GetCursorPos();
428 m_strEdit.erase(iPos - 1, 1);
434 void CGUIDialogKeyboardGeneric::OnClickButton(int iButtonControl)
436 if (iButtonControl == CTL_BUTTON_BACKSPACE)
441 Character(GetCharacter(iButtonControl));
444 void CGUIDialogKeyboardGeneric::OnRemoteNumberClick(int key)
446 unsigned int now = CTimeUtils::GetFrameTime();
448 if (m_lastRemoteClickTime)
449 { // a remote key has been pressed
450 if (key != m_lastRemoteKeyClicked || m_lastRemoteClickTime + REMOTE_SMS_DELAY < now)
451 { // a different key was clicked than last time, or we have timed out
452 m_lastRemoteKeyClicked = key;
454 // reset our shift and symbol states, and update our label to ensure the search filter is sent
455 ResetShiftAndSymbols();
459 { // same key as last time within the appropriate time period
465 { // key is pressed for the first time
466 m_lastRemoteKeyClicked = key;
470 int arrayIndex = key - REMOTE_0;
471 m_indexInSeries = m_indexInSeries % strlen(s_charsSeries[arrayIndex]);
472 m_lastRemoteClickTime = now;
474 // Select the character that will be pressed
475 const char* characterPressed = s_charsSeries[arrayIndex];
476 characterPressed += m_indexInSeries;
478 // use caps where appropriate
479 char ch = *characterPressed;
480 bool caps = (m_keyType == CAPS && !m_bShift) || (m_keyType == LOWER && m_bShift);
481 if (!caps && *characterPressed >= 'A' && *characterPressed <= 'Z')
486 char CGUIDialogKeyboardGeneric::GetCharacter(int iButton)
489 if (iButton >= 48 && iButton <= 57)
491 if (m_keyType == SYMBOLS)
494 return symbol_map[iButton -48];
497 return (char)iButton;
499 else if (iButton == 32) // space
500 return (char)iButton;
501 else if (iButton >= 65 && iButton < 91)
503 if (m_keyType == SYMBOLS)
506 return symbol_map[iButton - 65 + 10];
508 if ((m_keyType == CAPS && m_bShift) || (m_keyType == LOWER && !m_bShift))
513 { // turn off the shift key
516 return (char) iButton;
519 { // check for symbols
520 for (unsigned int i = 0; i < NUM_SYMBOLS; i++)
521 if (iButton == symbolButtons[i])
522 return (char)iButton;
527 void CGUIDialogKeyboardGeneric::UpdateButtons()
530 { // show the button depressed
531 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CTL_BUTTON_SHIFT);
536 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CTL_BUTTON_SHIFT);
539 if (m_keyType == CAPS)
541 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CTL_BUTTON_CAPS);
546 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CTL_BUTTON_CAPS);
549 if (m_keyType == SYMBOLS)
551 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CTL_BUTTON_SYMBOLS);
556 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CTL_BUTTON_SYMBOLS);
562 CStdString aLabel = szLabel;
565 for (int iButton = 48; iButton <= 57; iButton++)
567 if (m_keyType == SYMBOLS)
568 aLabel[0] = symbol_map[iButton - 48];
571 SetControlLabel(iButton, aLabel);
574 // set correct alphabet characters...
576 for (int iButton = 65; iButton <= 90; iButton++)
578 // set the correct case...
579 if ((m_keyType == CAPS && m_bShift) || (m_keyType == LOWER && !m_bShift))
581 aLabel[0] = iButton + 32;
583 else if (m_keyType == SYMBOLS)
585 aLabel[0] = symbol_map[iButton - 65 + 10];
591 SetControlLabel(iButton, aLabel);
593 for (unsigned int i = 0; i < NUM_SYMBOLS; i++)
595 aLabel[0] = symbolButtons[i];
596 SetControlLabel(symbolButtons[i], aLabel);
601 void CGUIDialogKeyboardGeneric::OnDeinitWindow(int nextWindowID)
604 CGUIDialog::OnDeinitWindow(nextWindowID);
605 // reset the heading (we don't always have this)
608 g_Windowing.EnableTextInput(false);
609 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Input, "xbmc", "OnInputFinished");
612 void CGUIDialogKeyboardGeneric::MoveCursor(int iAmount)
614 if (!m_strEditing.IsEmpty())
616 SetCursorPos(GetCursorPos() + iAmount);
619 void CGUIDialogKeyboardGeneric::SetCursorPos(int iPos)
623 else if (iPos > (int)m_strEdit.size())
624 iPos = (int)m_strEdit.size();
626 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
629 pEdit->SetCursorPos(m_iCursorPos + (m_hiddenInput ? 0 : m_iEditingOffset));
633 int CGUIDialogKeyboardGeneric::GetCursorPos() const
638 void CGUIDialogKeyboardGeneric::OnSymbols()
640 if (m_keyType == SYMBOLS)
647 void CGUIDialogKeyboardGeneric::OnShift()
649 m_bShift = !m_bShift;
653 void CGUIDialogKeyboardGeneric::OnIPAddress()
655 // find any IP address in the current string if there is any
656 // We match to #.#.#.#
657 CStdString utf8String;
658 g_charsetConverter.wToUTF8(m_strEdit, utf8String);
661 reg.RegComp("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+");
662 int start = reg.RegFind(utf8String.c_str());
666 length = reg.GetSubLength(0);
667 ip = utf8String.Mid(start, length);
670 start = utf8String.size();
671 if (CGUIDialogNumeric::ShowAndGetIPAddress(ip, g_localizeStrings.Get(14068)))
673 utf8String = utf8String.Left(start) + ip + utf8String.Mid(start + length);
674 g_charsetConverter.utf8ToW(utf8String, m_strEdit);
676 CGUILabelControl* pEdit = ((CGUILabelControl*)GetControl(CTL_LABEL_EDIT));
678 pEdit->SetCursorPos(m_strEdit.size());
682 void CGUIDialogKeyboardGeneric::ResetShiftAndSymbols()
684 if (m_bShift) OnShift();
685 if (m_keyType == SYMBOLS) OnSymbols();
686 m_lastRemoteClickTime = 0;
689 const char* CGUIDialogKeyboardGeneric::s_charsSeries[10] = { " 0!@#$%^&*()[]{}<>/\\|", ".,1;:\'\"-+_=?`~", "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
691 void CGUIDialogKeyboardGeneric::SetControlLabel(int id, const CStdString &label)
692 { // find all controls with this id, and set all their labels
693 CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), id);
694 message.SetLabel(label);
695 for (unsigned int i = 0; i < m_children.size(); i++)
697 if (m_children[i]->GetID() == id || m_children[i]->IsGroup())
698 m_children[i]->OnMessage(message);
702 void CGUIDialogKeyboardGeneric::OnOK()
704 m_bIsConfirmed = true;
708 void CGUIDialogKeyboardGeneric::SetHeading(const std::string &heading)
710 m_strHeading = heading;
713 int CGUIDialogKeyboardGeneric::GetWindowId() const
718 void CGUIDialogKeyboardGeneric::Cancel()
720 m_bIsConfirmed = false;
724 bool CGUIDialogKeyboardGeneric::ShowAndGetInput(char_callback_t pCallback, const std::string &initialString, std::string &typedString, const std::string &heading, bool bHiddenInput)
726 CGUIDialogKeyboardGeneric *pKeyboard = (CGUIDialogKeyboardGeneric*)g_windowManager.GetWindow(WINDOW_DIALOG_KEYBOARD);
731 m_pCharCallback = pCallback;
733 pKeyboard->Initialize();
734 pKeyboard->SetHeading(heading);
735 pKeyboard->SetHiddenInput(bHiddenInput);
736 pKeyboard->SetText(initialString);
737 // do this using a thread message to avoid render() conflicts
738 ThreadMessage tMsg = {TMSG_DIALOG_DOMODAL, WINDOW_DIALOG_KEYBOARD, (unsigned int)g_windowManager.GetActiveWindow()};
739 CApplicationMessenger::Get().SendMessage(tMsg, true);
742 // If have text - update this.
743 if (pKeyboard->IsConfirmed())
745 typedString = pKeyboard->GetText();
751 void CGUIDialogKeyboardGeneric::OnPasteClipboard(void)
753 CStdStringW unicode_text;
754 CStdStringA utf8_text;
756 // Get text from the clipboard
757 utf8_text = g_Windowing.GetClipboardText();
759 // Insert the pasted text at the current cursor position.
760 if (utf8_text.length() > 0)
762 g_charsetConverter.utf8ToW(utf8_text, unicode_text);
764 int i = GetCursorPos();
765 CStdStringW left_end = m_strEdit.Left(i);
766 CStdStringW right_end = m_strEdit.Right(m_strEdit.length() - i);
768 m_strEdit = left_end;
769 m_strEdit.append(unicode_text);
770 m_strEdit.append(right_end);
772 MoveCursor(unicode_text.length());