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
.empty() && (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
.empty())
159 uint8_t b
= action
.GetID() & 0xFF;
160 if (b
== XBMCVK_HOME
)
164 else if (b
== XBMCVK_END
)
166 SetCursorPos(m_strEdit
.size());
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() < (int)m_strEdit
.size())
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() < (int)m_strEdit
.size())
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
);
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(), 1, 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
.empty())
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
.empty())
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
.empty())
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
.substr(start
, length
);
670 start
= utf8String
.size();
671 if (CGUIDialogNumeric::ShowAndGetIPAddress(ip
, g_localizeStrings
.Get(14068)))
673 utf8String
= utf8String
.substr(0, start
) + ip
.c_str() + utf8String
.substr(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 size_t i
= GetCursorPos();
765 if (i
> m_strEdit
.size())
766 i
= m_strEdit
.size();
767 CStdStringW left_end
= m_strEdit
.substr(0, i
);
768 CStdStringW right_end
= m_strEdit
.substr(i
);
770 m_strEdit
= left_end
;
771 m_strEdit
.append(unicode_text
);
772 m_strEdit
.append(right_end
);
774 MoveCursor(unicode_text
.length());