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 "GUIEditControl.h"
23 #include "GUIWindowManager.h"
24 #include "utils/CharsetConverter.h"
25 #include "dialogs/GUIDialogKeyboard.h"
26 #include "dialogs/GUIDialogNumeric.h"
27 #include "input/XBMC_vkeys.h"
28 #include "LocalizeStrings.h"
29 #include "XBDateTime.h"
30 #include "utils/md5.h"
32 #if defined(TARGET_DARWIN)
33 #include "CocoaInterface.h"
36 const char* CGUIEditControl::smsLetters[10] = { " !@#$%^&*()[]{}<>/\\|0", ".,;:\'\"-+_=?`~1", "abc2", "def3", "ghi4", "jkl5", "mno6", "pqrs7", "tuv8", "wxyz9" };
37 const unsigned int CGUIEditControl::smsDelay = 1000;
45 CGUIEditControl::CGUIEditControl(int parentID, int controlID, float posX, float posY,
46 float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
47 const CLabelInfo& labelInfo, const std::string &text)
48 : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
54 void CGUIEditControl::DefaultConstructor()
56 ControlType = GUICONTROL_EDIT;
58 m_textWidth = GetWidth();
62 m_inputType = INPUT_TYPE_TEXT;
65 m_label.SetAlign(m_label.GetLabelInfo().align & XBFONT_CENTER_Y); // left align
66 m_label2.GetLabelInfo().offsetX = 0;
70 CGUIEditControl::CGUIEditControl(const CGUIButtonControl &button)
71 : CGUIButtonControl(button)
76 CGUIEditControl::~CGUIEditControl(void)
80 bool CGUIEditControl::OnMessage(CGUIMessage &message)
82 if (message.GetMessage() == GUI_MSG_SET_TYPE)
84 SetInputType((INPUT_TYPE)message.GetParam1(), (int)message.GetParam2());
87 else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
89 message.SetLabel(GetLabel2());
92 else if (message.GetMessage() == GUI_MSG_SETFOCUS ||
93 message.GetMessage() == GUI_MSG_LOSTFOCUS)
97 else if (message.GetMessage() == GUI_MSG_SET_TEXT)
99 SetLabel2(message.GetLabel());
102 return CGUIButtonControl::OnMessage(message);
105 bool CGUIEditControl::OnAction(const CAction &action)
109 if (m_inputType != INPUT_TYPE_READONLY)
111 if (action.GetID() == ACTION_BACKSPACE)
117 m_text2.erase(--m_cursorPos, 1);
122 else if (action.GetID() == ACTION_MOVE_LEFT)
131 else if (action.GetID() == ACTION_MOVE_RIGHT)
133 if ((unsigned int) m_cursorPos < m_text2.size())
140 else if (action.GetID() == ACTION_PASTE)
145 else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_ASCII)
147 // input from the keyboard (vkey, not ascii)
148 BYTE b = action.GetID() & 0xFF;
149 if (b == XBMCVK_HOME)
155 else if (b == XBMCVK_END)
157 m_cursorPos = m_text2.length();
161 if (b == XBMCVK_LEFT && m_cursorPos > 0)
167 if (b == XBMCVK_RIGHT && m_cursorPos < m_text2.length())
173 if (b == XBMCVK_DELETE)
175 if (m_cursorPos < m_text2.length())
178 m_text2.erase(m_cursorPos, 1);
183 if (b == XBMCVK_BACK)
188 m_text2.erase(--m_cursorPos, 1);
194 else if (action.GetID() >= KEY_ASCII)
196 // input from the keyboard
197 switch (action.GetUnicode())
204 // enter - send click message, but otherwise ignore
205 SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
209 { // escape - fallthrough to default action
210 return CGUIButtonControl::OnAction(action);
218 m_text2.erase(--m_cursorPos, 1);
225 m_text2.insert(m_text2.begin() + m_cursorPos++, (WCHAR)action.GetUnicode());
232 else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
233 { // input from the remote
235 if (m_inputType == INPUT_TYPE_FILTER)
236 { // filtering - use single number presses
237 m_text2.insert(m_text2.begin() + m_cursorPos++, L'0' + (action.GetID() - REMOTE_0));
241 OnSMSCharacter(action.GetID() - REMOTE_0);
245 return CGUIButtonControl::OnAction(action);
248 void CGUIEditControl::OnClick()
250 // we received a click - it's not from the keyboard, so pop up the virtual keyboard, unless
251 // that is where we reside!
252 if (GetParentID() == WINDOW_DIALOG_KEYBOARD)
256 g_charsetConverter.wToUTF8(m_text2, utf8);
257 bool textChanged = false;
258 CStdString heading = g_localizeStrings.Get(m_inputHeading ? m_inputHeading : 16028);
261 case INPUT_TYPE_READONLY:
264 case INPUT_TYPE_NUMBER:
265 textChanged = CGUIDialogNumeric::ShowAndGetNumber(utf8, heading);
267 case INPUT_TYPE_SECONDS:
268 textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420));
270 case INPUT_TYPE_DATE:
273 dateTime.SetFromDBDate(utf8);
274 if (dateTime < CDateTime(2000,1, 1, 0, 0, 0))
275 dateTime = CDateTime(2000, 1, 1, 0, 0, 0);
277 dateTime.GetAsSystemTime(date);
278 if (CGUIDialogNumeric::ShowAndGetDate(date, g_localizeStrings.Get(21420)))
280 dateTime = CDateTime(date);
281 utf8 = dateTime.GetAsDBDate();
286 case INPUT_TYPE_IPADDRESS:
287 textChanged = CGUIDialogNumeric::ShowAndGetIPAddress(utf8, heading);
289 case INPUT_TYPE_SEARCH:
290 textChanged = CGUIDialogKeyboard::ShowAndGetFilter(utf8, true);
292 case INPUT_TYPE_FILTER:
293 textChanged = CGUIDialogKeyboard::ShowAndGetFilter(utf8, false);
295 case INPUT_TYPE_PASSWORD_MD5:
296 utf8 = ""; // TODO: Ideally we'd send this to the keyboard and tell the keyboard we have this type of input
298 case INPUT_TYPE_TEXT:
300 textChanged = CGUIDialogKeyboard::ShowAndGetInput(utf8, heading, true, m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5);
306 g_charsetConverter.utf8ToW(utf8, m_text2);
307 m_cursorPos = m_text2.size();
309 m_cursorPos = m_text2.size();
313 void CGUIEditControl::UpdateText(bool sendUpdate)
318 SEND_CLICK_MESSAGE(GetID(), GetParentID(), 0);
320 m_textChangeActions.Execute(GetID(), GetParentID());
325 void CGUIEditControl::SetInputType(CGUIEditControl::INPUT_TYPE type, int heading)
328 m_inputHeading = heading;
329 // TODO: Verify the current input string?
332 void CGUIEditControl::RecalcLabelPosition()
334 // ensure that our cursor is within our width
337 CStdStringW text = GetDisplayedText();
338 m_textWidth = m_label.CalcTextWidth(text + L'|');
339 float beforeCursorWidth = m_label.CalcTextWidth(text.Left(m_cursorPos));
340 float afterCursorWidth = m_label.CalcTextWidth(text.Left(m_cursorPos) + L'|');
341 float leftTextWidth = m_label.GetRenderRect().Width();
342 float maxTextWidth = m_label.GetMaxWidth();
343 if (leftTextWidth > 0)
344 maxTextWidth -= leftTextWidth + spaceWidth;
346 // if skinner forgot to set height :p
347 if (m_height == 0 && m_label.GetLabelInfo().font)
348 m_height = m_label.GetLabelInfo().font->GetTextHeight(1);
350 if (m_textWidth > maxTextWidth)
351 { // we render taking up the full width, so make sure our cursor position is
352 // within the render window
353 if (m_textOffset + afterCursorWidth > maxTextWidth)
355 // move the position to the left (outside of the viewport)
356 m_textOffset = maxTextWidth - afterCursorWidth;
358 else if (m_textOffset + beforeCursorWidth < 0) // offscreen to the left
360 // otherwise use original position
361 m_textOffset = -beforeCursorWidth;
363 else if (m_textOffset + m_textWidth < maxTextWidth)
364 { // we have more text than we're allowed, but we aren't filling all the space
365 m_textOffset = maxTextWidth - m_textWidth;
372 void CGUIEditControl::ProcessText(unsigned int currentTime)
374 if (m_smsTimer.GetElapsedMilliseconds() > smsDelay)
379 m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
380 m_label.SetText(m_info.GetLabel(GetParentID()));
381 RecalcLabelPosition();
384 bool changed = false;
386 m_clipRect.x1 = m_label.GetRenderRect().x1;
387 m_clipRect.x2 = m_clipRect.x1 + m_label.GetMaxWidth();
388 m_clipRect.y1 = m_posY;
389 m_clipRect.y2 = m_posY + m_height;
391 // start by rendering the normal text
392 float leftTextWidth = m_label.GetRenderRect().Width();
393 if (leftTextWidth > 0)
395 // render the text on the left
396 changed |= m_label.SetColor(GetTextColor());
397 changed |= m_label.Process(currentTime);
399 m_clipRect.x1 += leftTextWidth + spaceWidth;
402 if (g_graphicsContext.SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
404 uint32_t align = m_label.GetLabelInfo().align & XBFONT_CENTER_Y; // start aligned left
405 if (m_label2.GetTextWidth() < m_clipRect.Width())
406 { // align text as our text fits
407 if (leftTextWidth > 0)
408 { // right align as we have 2 labels
409 align |= XBFONT_RIGHT;
412 { // align by whatever the skinner requests
413 align |= (m_label2.GetLabelInfo().align & 3);
416 CStdStringW text = GetDisplayedText();
417 // add the cursor if we're focused
418 if (HasFocus() && m_inputType != INPUT_TYPE_READONLY)
421 if ((m_focusCounter % 64) > 32)
424 col = L"[COLOR 00FFFFFF]|[/COLOR]";
425 text.Insert(m_cursorPos, col);
428 changed |= m_label2.SetMaxRect(m_clipRect.x1 + m_textOffset, m_posY, m_clipRect.Width() - m_textOffset, m_height);
430 changed |= m_label2.SetText(m_hintInfo.GetLabel(GetParentID()));
432 changed |= m_label2.SetTextW(text);
433 changed |= m_label2.SetAlign(align);
434 changed |= m_label2.SetColor(GetTextColor());
435 changed |= m_label2.SetOverflow(CGUILabel::OVER_FLOW_CLIP);
436 changed |= m_label2.Process(currentTime);
437 g_graphicsContext.RestoreClipRegion();
443 void CGUIEditControl::RenderText()
447 if (g_graphicsContext.SetClipRegion(m_clipRect.x1, m_clipRect.y1, m_clipRect.Width(), m_clipRect.Height()))
450 g_graphicsContext.RestoreClipRegion();
454 void CGUIEditControl::SetHint(const CGUIInfoLabel& hint)
459 CStdStringW CGUIEditControl::GetDisplayedText() const
461 if (m_inputType == INPUT_TYPE_PASSWORD || m_inputType == INPUT_TYPE_PASSWORD_MD5)
464 text.append(m_text2.size(), L'*');
470 void CGUIEditControl::ValidateCursor()
472 if (m_cursorPos > m_text2.size())
473 m_cursorPos = m_text2.size();
476 void CGUIEditControl::SetLabel(const std::string &text)
478 CGUIButtonControl::SetLabel(text);
482 void CGUIEditControl::SetLabel2(const std::string &text)
485 g_charsetConverter.utf8ToW(text, newText);
486 if (newText != m_text2)
488 m_isMD5 = m_inputType == INPUT_TYPE_PASSWORD_MD5;
490 m_cursorPos = m_text2.size();
495 CStdString CGUIEditControl::GetLabel2() const
498 g_charsetConverter.wToUTF8(m_text2, text);
499 if (m_inputType == INPUT_TYPE_PASSWORD_MD5 && !m_isMD5)
500 return XBMC::XBMC_MD5::GetMD5(text);
504 bool CGUIEditControl::ClearMD5()
506 if (m_inputType != INPUT_TYPE_PASSWORD_MD5 || !m_isMD5)
515 unsigned int CGUIEditControl::GetCursorPosition() const
520 void CGUIEditControl::SetCursorPosition(unsigned int iPosition)
522 m_cursorPos = iPosition;
525 void CGUIEditControl::OnSMSCharacter(unsigned int key)
528 bool sendUpdate = false;
529 if (m_smsTimer.IsRunning())
531 // we're already entering an SMS character
532 if (key != m_smsLastKey || m_smsTimer.GetElapsedMilliseconds() > smsDelay)
533 { // a different key was clicked than last time, or we have timed out
539 { // same key as last time within the appropriate time period
542 m_text2.erase(--m_cursorPos, 1);
546 { // key is pressed for the first time
551 m_smsKeyIndex = m_smsKeyIndex % strlen(smsLetters[key]);
553 m_text2.insert(m_text2.begin() + m_cursorPos++, smsLetters[key][m_smsKeyIndex]);
554 UpdateText(sendUpdate);
555 m_smsTimer.StartZero();
558 void CGUIEditControl::OnPasteClipboard()
560 #if defined(TARGET_DARWIN_OSX)
561 const char *szStr = Cocoa_Paste();
565 m_cursorPos+=strlen(szStr);
569 if (OpenClipboard(g_hWnd))
571 HGLOBAL hglb = GetClipboardData(CF_TEXT);
574 LPTSTR lptstr = (LPTSTR)GlobalLock(hglb);
577 m_text2 = (char*)lptstr;