/* * Copyright (C) 2005-2013 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, see * . * */ #include "GUIDialogNumeric.h" #include "guilib/GUILabelControl.h" #include "utils/md5.h" #include "guilib/GUIWindowManager.h" #include "GUIDialogOK.h" #include "input/XBMC_vkeys.h" #include "utils/StringUtils.h" #include "guilib/Key.h" #include "guilib/LocalizeStrings.h" #include "interfaces/AnnouncementManager.h" #define CONTROL_HEADING_LABEL 1 #define CONTROL_INPUT_LABEL 4 #define CONTROL_NUM0 10 #define CONTROL_NUM9 19 #define CONTROL_PREVIOUS 20 #define CONTROL_ENTER 21 #define CONTROL_NEXT 22 #define CONTROL_BACKSPACE 23 CGUIDialogNumeric::CGUIDialogNumeric(void) : CGUIDialog(WINDOW_DIALOG_NUMERIC, "DialogNumeric.xml") { m_bConfirmed = false; m_bCanceled = false; m_mode = INPUT_PASSWORD; m_block = 0; memset(&m_datetime, 0, sizeof(SYSTEMTIME)); m_dirty = false; m_loadType = KEEP_IN_MEMORY; } CGUIDialogNumeric::~CGUIDialogNumeric(void) { } void CGUIDialogNumeric::OnInitWindow() { CGUIDialog::OnInitWindow(); CVariant data; switch (m_mode) { case INPUT_TIME: data["type"] = "time"; break; case INPUT_DATE: data["type"] = "date"; break; case INPUT_IP_ADDRESS: data["type"] = "ip"; break; case INPUT_PASSWORD: data["type"] = "numericpassword"; break; case INPUT_NUMBER: data["type"] = "number"; break; case INPUT_TIME_SECONDS: data["type"] = "seconds"; break; default: data["type"] = "keyboard"; break; } const CGUIControl *control = GetControl(CONTROL_HEADING_LABEL); if (control != NULL) data["title"] = control->GetDescription(); data["value"] = GetOutput(); ANNOUNCEMENT::CAnnouncementManager::Get().Announce(ANNOUNCEMENT::Input, "xbmc", "OnInputRequested", data); } void CGUIDialogNumeric::OnDeinitWindow(int nextWindowID) { // call base class CGUIDialog::OnDeinitWindow(nextWindowID); ANNOUNCEMENT::CAnnouncementManager::Get().Announce(ANNOUNCEMENT::Input, "xbmc", "OnInputFinished"); } bool CGUIDialogNumeric::OnAction(const CAction &action) { if (action.GetID() == ACTION_NEXT_ITEM) OnNext(); else if (action.GetID() == ACTION_PREV_ITEM) OnPrevious(); else if (action.GetID() == ACTION_BACKSPACE) OnBackSpace(); else if (action.GetID() == ACTION_ENTER) OnOK(); else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9) OnNumber(action.GetID() - REMOTE_0); else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_ASCII) { // input from the keyboard (vkey, not ascii) BYTE b = action.GetID() & 0xFF; if (b == XBMCVK_LEFT) OnPrevious(); else if (b == XBMCVK_RIGHT) OnNext(); else if (b == XBMCVK_RETURN || b == XBMCVK_NUMPADENTER) OnOK(); else if (b == XBMCVK_BACK) OnBackSpace(); else if (b == XBMCVK_ESCAPE) OnCancel(); } else if (action.GetID() >= KEY_ASCII) // FIXME make it KEY_UNICODE { // input from the keyboard if (action.GetUnicode() == 10 || action.GetUnicode() == 13) OnOK(); // enter else if (action.GetUnicode() == 8) OnBackSpace(); // backspace else if (action.GetUnicode() == 27) OnCancel(); // escape else if (action.GetUnicode() >= 48 && action.GetUnicode() < 58) // number OnNumber(action.GetUnicode() - 48); } else return CGUIDialog::OnAction(action); return true; } bool CGUIDialogNumeric::OnBack(int actionID) { OnCancel(); return true; } bool CGUIDialogNumeric::OnMessage(CGUIMessage& message) { switch ( message.GetMessage() ) { case GUI_MSG_WINDOW_INIT: { m_bConfirmed = false; m_bCanceled = false; m_dirty = false; return CGUIDialog::OnMessage(message); } break; case GUI_MSG_CLICKED: { int iControl = message.GetSenderId(); m_bConfirmed = false; m_bCanceled = false; if (CONTROL_NUM0 <= iControl && iControl <= CONTROL_NUM9) // User numeric entry via dialog button UI { OnNumber(iControl - 10); return true; } else if (iControl == CONTROL_PREVIOUS) { OnPrevious(); return true; } else if (iControl == CONTROL_NEXT) { OnNext(); return true; } else if (iControl == CONTROL_BACKSPACE) { OnBackSpace(); return true; } else if (iControl == CONTROL_ENTER) { OnOK(); return true; } } break; case GUI_MSG_SET_TEXT: SetMode(m_mode, message.GetLabel()); // close the dialog if requested if (message.GetParam1() > 0) OnOK(); break; } return CGUIDialog::OnMessage(message); } void CGUIDialogNumeric::OnBackSpace() { if (!m_dirty && m_block) { m_block--; return; } if (m_mode == INPUT_NUMBER || m_mode == INPUT_PASSWORD) { // just go back one character if (!m_number.empty()) m_number.erase(m_number.size() - 1); } else if (m_mode == INPUT_IP_ADDRESS) { if (m_ip[m_block]) m_ip[m_block] /= 10; else if (m_block) { m_block--; m_dirty = false; } } else if (m_mode == INPUT_TIME) { if (m_block == 0) m_datetime.wHour /= 10; else if (m_datetime.wMinute) m_datetime.wMinute /= 10; else { m_block = 0; m_dirty = false; } } else if (m_mode == INPUT_TIME_SECONDS) { if (m_block == 0) m_datetime.wHour /= 10; else if (m_block == 1) { if (m_datetime.wMinute) m_datetime.wMinute /= 10; else { m_block = 0; m_dirty = false; } } else if (m_datetime.wSecond) m_datetime.wMinute /= 10; else { m_block = 0; m_dirty = false; } } else if (m_mode == INPUT_DATE) { if (m_block == 0) m_datetime.wDay /= 10; else if (m_block == 1) { if (m_datetime.wMonth) m_datetime.wMonth /= 10; else { m_block = 0; m_dirty = false; } } else if (m_datetime.wYear) // m_block == 2 m_datetime.wYear /= 10; else { m_block = 1; m_dirty = false; } } } void CGUIDialogNumeric::OnPrevious() { if (m_block) m_block--; m_dirty = false; } void CGUIDialogNumeric::OnNext() { if (m_mode == INPUT_IP_ADDRESS && m_block==0 && m_ip[0]==0) return; if (m_block < m_lastblock) m_block++; m_dirty = false; if (m_mode == INPUT_DATE) VerifyDate(m_block == 2); } void CGUIDialogNumeric::FrameMove() { std::string strLabel; unsigned int start = 0; unsigned int end = 0; if (m_mode == INPUT_PASSWORD) { for (unsigned int i=0; i < m_number.size(); i++) strLabel += '*'; } else if (m_mode == INPUT_NUMBER) { // simple - just render text directly strLabel = m_number; } else if (m_mode == INPUT_TIME) { // format up the time strLabel = StringUtils::Format("%2d:%02d", m_datetime.wHour, m_datetime.wMinute); start = m_block * 3; end = m_block * 3 + 2; } else if (m_mode == INPUT_TIME_SECONDS) { // format up the time strLabel = StringUtils::Format("%2d:%02d:%02d", m_datetime.wHour, m_datetime.wMinute, m_datetime.wSecond); start = m_block * 3; end = m_block * 3 + 2; } else if (m_mode == INPUT_DATE) { // format up the date strLabel = StringUtils::Format("%2d/%2d/%4d", m_datetime.wDay, m_datetime.wMonth, m_datetime.wYear); start = m_block * 3; end = m_block * 3 + 2; if (m_block == 2) end = m_block * 3 + 4; } else if (m_mode == INPUT_IP_ADDRESS) { // format up the date strLabel = StringUtils::Format("%3d.%3d.%3d.%3d", m_ip[0], m_ip[1], m_ip[2], m_ip[3]); start = m_block * 4; end = m_block * 4 + 3; } CGUILabelControl *pLabel = dynamic_cast(GetControl(CONTROL_INPUT_LABEL)); if (pLabel) { pLabel->SetLabel(strLabel); pLabel->SetHighlight(start, end); } CGUIDialog::FrameMove(); } void CGUIDialogNumeric::OnNumber(unsigned int num) { ResetAutoClose(); if (m_mode == INPUT_NUMBER || m_mode == INPUT_PASSWORD) { m_number += num + '0'; } else if (m_mode == INPUT_TIME) { if (m_block == 0) // hour { if (m_dirty) // have input the first digit { if (m_datetime.wHour < 2 || num < 4) { m_datetime.wHour *= 10; m_datetime.wHour += num; } else m_datetime.wHour = num; m_block = 1; // move to minutes m_dirty = false; } else // this is the first digit { m_datetime.wHour = num; if (num > 2) { m_block = 1; // move to minutes m_dirty = false; } else m_dirty = true; } } else // minute { if (m_dirty) // have input the first digit { m_datetime.wMinute *= 10; m_datetime.wMinute += num; m_block = 0; // move to hours m_dirty = false; } else // this is the first digit { m_datetime.wMinute = num; if (num > 5) { m_block = 0; // move to hours m_dirty = false; } else m_dirty = true; } } } else if (m_mode == INPUT_TIME_SECONDS) { if (m_block == 0) // hour { if (m_dirty) // have input the first digit { m_datetime.wHour *= 10; m_datetime.wHour += num; m_block = 1; // move to minutes - allows up to 99 hours m_dirty = false; } else // this is the first digit { m_datetime.wHour = num; m_dirty = true; } } else if (m_block == 1) // minute { if (m_dirty) // have input the first digit { m_datetime.wMinute *= 10; m_datetime.wMinute += num; m_block = 2; // move to seconds - allows up to 99 minutes m_dirty = false; } else // this is the first digit { m_datetime.wMinute = num; if (num > 5) { m_block = 2; // move to seconds m_dirty = false; } else m_dirty = true; } } else // seconds { if (m_dirty) // have input the first digit { m_datetime.wSecond *= 10; m_datetime.wSecond += num; m_block = 0; // move to hours m_dirty = false; } else // this is the first digit { m_datetime.wSecond = num; if (num > 5) { m_block = 0; // move to hours m_dirty = false; } else m_dirty = true; } } } else if (m_mode == INPUT_DATE) { if (m_block == 0) // day of month { if (m_dirty && (m_datetime.wDay < 3 || num < 2)) { m_datetime.wDay *= 10; m_datetime.wDay += num; } else m_datetime.wDay = num; if (m_datetime.wDay > 3) { m_block = 1; // move to months m_dirty = false; } else m_dirty = true; } else if (m_block == 1) // months { if (m_dirty && num < 3) { m_datetime.wMonth *= 10; m_datetime.wMonth += num; } else m_datetime.wMonth = num; if (m_datetime.wMonth > 1) { VerifyDate(false); m_block = 2; // move to year m_dirty = false; } else m_dirty = true; } else // year { if (m_dirty && m_datetime.wYear < 1000) // have taken input { m_datetime.wYear *= 10; m_datetime.wYear += num; } else m_datetime.wYear = num; if (m_datetime.wYear > 1000) { VerifyDate(true); m_block = 0; // move to day of month m_dirty = false; } else m_dirty = true; } } else if (m_mode == INPUT_IP_ADDRESS) { if (m_dirty && ((m_ip[m_block] < 25) || (m_ip[m_block] == 25 && num < 6) || !(m_block==0 && num==0))) { m_ip[m_block] *= 10; m_ip[m_block] += num; } else m_ip[m_block] = num; if (m_ip[m_block] > 25 || (m_ip[m_block] == 0 && num == 0)) { m_block++; if (m_block > 3) m_block = 0; m_dirty = false; } else m_dirty = true; } } void CGUIDialogNumeric::SetMode(INPUT_MODE mode, void *initial) { m_mode = mode; m_block = 0; m_lastblock = 0; if (m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS || m_mode == INPUT_DATE) { m_datetime = *(SYSTEMTIME *)initial; m_lastblock = (m_mode != INPUT_TIME) ? 2 : 1; } else if (m_mode == INPUT_IP_ADDRESS) { m_lastblock = 3; m_ip[0] = m_ip[1] = m_ip[2] = m_ip[3] = 0; // copy ip string into numeric form std::string ip = *(std::string *)initial; unsigned int block = 0; for (unsigned int i=0; i < ip.size(); i++) { if (ip[i] == '.') { block++; if (block > m_lastblock) break; } else if (isdigit(ip[i])) { m_ip[block] *= 10; m_ip[block] += ip[i] - '0'; } } } else if (m_mode == INPUT_NUMBER || m_mode == INPUT_PASSWORD) m_number = *(std::string *)initial; } void CGUIDialogNumeric::SetMode(INPUT_MODE mode, const std::string &initial) { m_mode = mode; m_block = 0; m_lastblock = 0; if (m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS || m_mode == INPUT_DATE) { CDateTime dateTime; if (m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS) { // check if we have a pure number if (initial.find_first_not_of("0123456789") == std::string::npos) { long seconds = strtol(initial.c_str(), NULL, 10); dateTime = seconds; } else { std::string tmp = initial; // if we are handling seconds and if the string only contains // "mm:ss" we need to add dummy "hh:" to get "hh:mm:ss" if (m_mode == INPUT_TIME_SECONDS && tmp.size() <= 5) tmp = "00:" + tmp; dateTime.SetFromDBTime(tmp); } } else if (m_mode == INPUT_DATE) { std::string tmp = initial; StringUtils::Replace(tmp, '/', '.'); dateTime.SetFromDBDate(tmp); } if (!dateTime.IsValid()) return; dateTime.GetAsSystemTime(m_datetime); m_lastblock = (m_mode == INPUT_DATE) ? 2 : 1; } else SetMode(mode, (void*)&initial); } void CGUIDialogNumeric::GetOutput(void *output) const { if (!output) return; if (m_mode == INPUT_TIME || m_mode == INPUT_TIME_SECONDS || m_mode == INPUT_DATE) memcpy(output, &m_datetime, sizeof(m_datetime)); else if (m_mode == INPUT_IP_ADDRESS) *(std::string *)output = StringUtils::Format("%d.%d.%d.%d", m_ip[0], m_ip[1], m_ip[2], m_ip[3]); else if (m_mode == INPUT_NUMBER || m_mode == INPUT_PASSWORD) *(std::string *)output = m_number; } std::string CGUIDialogNumeric::GetOutput() const { std::string output; if (m_mode == INPUT_DATE) output = StringUtils::Format("%02i/%02i/%04i", m_datetime.wDay, m_datetime.wMonth, m_datetime.wYear); else if (m_mode == INPUT_TIME) output = StringUtils::Format("%i:%02i", m_datetime.wHour, m_datetime.wMinute); else if (m_mode == INPUT_TIME_SECONDS) output = StringUtils::Format("%i:%02i:%02i", m_datetime.wHour, m_datetime.wMinute, m_datetime.wSecond); else GetOutput(&output); return output; } bool CGUIDialogNumeric::ShowAndGetSeconds(std::string &timeString, const std::string &heading) { CGUIDialogNumeric *pDialog = (CGUIDialogNumeric *)g_windowManager.GetWindow(WINDOW_DIALOG_NUMERIC); if (!pDialog) return false; int seconds = StringUtils::TimeStringToSeconds(timeString); SYSTEMTIME time = {0}; time.wHour = seconds / 3600; time.wMinute = (seconds - time.wHour * 3600) / 60; time.wSecond = seconds - time.wHour * 3600 - time.wMinute * 60; pDialog->SetMode(INPUT_TIME_SECONDS, (void *)&time); pDialog->SetHeading(heading); pDialog->DoModal(); if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) return false; pDialog->GetOutput(&time); seconds = time.wHour * 3600 + time.wMinute * 60 + time.wSecond; timeString = StringUtils::SecondsToTimeString(seconds); return true; } bool CGUIDialogNumeric::ShowAndGetTime(SYSTEMTIME &time, const std::string &heading) { CGUIDialogNumeric *pDialog = (CGUIDialogNumeric *)g_windowManager.GetWindow(WINDOW_DIALOG_NUMERIC); if (!pDialog) return false; pDialog->SetMode(INPUT_TIME, (void *)&time); pDialog->SetHeading(heading); pDialog->DoModal(); if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) return false; pDialog->GetOutput(&time); return true; } bool CGUIDialogNumeric::ShowAndGetDate(SYSTEMTIME &date, const std::string &heading) { CGUIDialogNumeric *pDialog = (CGUIDialogNumeric *)g_windowManager.GetWindow(WINDOW_DIALOG_NUMERIC); if (!pDialog) return false; pDialog->SetMode(INPUT_DATE, (void *)&date); pDialog->SetHeading(heading); pDialog->DoModal(); if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) return false; pDialog->GetOutput(&date); return true; } bool CGUIDialogNumeric::ShowAndGetIPAddress(std::string &IPAddress, const std::string &heading) { CGUIDialogNumeric *pDialog = (CGUIDialogNumeric *)g_windowManager.GetWindow(WINDOW_DIALOG_NUMERIC); if (!pDialog) return false; pDialog->SetMode(INPUT_IP_ADDRESS, (void *)&IPAddress); pDialog->SetHeading(heading); pDialog->DoModal(); if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) return false; pDialog->GetOutput(&IPAddress); return true; } bool CGUIDialogNumeric::ShowAndGetNumber(std::string& strInput, const std::string &strHeading, unsigned int iAutoCloseTimeoutMs /* = 0 */) { // Prompt user for password input CGUIDialogNumeric *pDialog = (CGUIDialogNumeric *)g_windowManager.GetWindow(WINDOW_DIALOG_NUMERIC); pDialog->SetHeading( strHeading ); pDialog->SetMode(INPUT_NUMBER, (void *)&strInput); if (iAutoCloseTimeoutMs) pDialog->SetAutoClose(iAutoCloseTimeoutMs); pDialog->DoModal(); if (!pDialog->IsAutoClosed() && (!pDialog->IsConfirmed() || pDialog->IsCanceled())) return false; pDialog->GetOutput(&strInput); return true; } // \brief Show numeric keypad twice to get and confirm a user-entered password string. // \param strNewPassword String to preload into the keyboard accumulator. Overwritten with user input if return=true. // \return true if successful display and user input entry/re-entry. false if unsuccessful display, no user input, or canceled editing. bool CGUIDialogNumeric::ShowAndVerifyNewPassword(std::string& strNewPassword) { // Prompt user for password input std::string strUserInput; if (!ShowAndVerifyInput(strUserInput, g_localizeStrings.Get(12340), false)) { // Show error to user saying the password entry was blank CGUIDialogOK::ShowAndGetInput(12357, 12358, 0, 0); // Password is empty/blank return false; } if (strUserInput.empty()) // user canceled out return false; // Prompt again for password input, this time sending previous input as the password to verify if (!ShowAndVerifyInput(strUserInput, g_localizeStrings.Get(12341), true)) { // Show error to user saying the password re-entry failed CGUIDialogOK::ShowAndGetInput(12357, 12344, 0, 0); // Password do not match return false; } // password entry and re-entry succeeded strNewPassword = strUserInput; return true; } // \brief Show numeric keypad and verify user input against strPassword. // \param strPassword Value to compare against user input. // \param strHeading String shown on dialog title. Converts to localized string if contains a positive integer. // \param iRetries If greater than 0, shows "Incorrect password, %d retries left" on dialog line 2, else line 2 is blank. // \return 0 if successful display and user input. 1 if unsuccessful input. -1 if no user input or canceled editing. int CGUIDialogNumeric::ShowAndVerifyPassword(std::string& strPassword, const std::string& strHeading, int iRetries) { std::string strTempHeading = strHeading; if (0 < iRetries) { // Show a string telling user they have iRetries retries left strTempHeading = StringUtils::Format("%s. %s %i %s", strHeading.c_str(), g_localizeStrings.Get(12342).c_str(), iRetries, g_localizeStrings.Get(12343).c_str()); } // make a copy of strPassword to prevent from overwriting it later std::string strPassTemp = strPassword; if (ShowAndVerifyInput(strPassTemp, strTempHeading, true)) return 0; // user entered correct password if (strPassTemp.empty()) return -1; // user canceled out return 1; // user must have entered an incorrect password } // \brief Show numeric keypad and verify user input against strToVerify. // \param strToVerify Value to compare against user input. // \param dlgHeading String shown on dialog title. // \param bVerifyInput If set as true we verify the users input versus strToVerify. // \return true if successful display and user input. false if unsuccessful display, no user input, or canceled editing. bool CGUIDialogNumeric::ShowAndVerifyInput(std::string& strToVerify, const std::string& dlgHeading, bool bVerifyInput) { // Prompt user for password input CGUIDialogNumeric *pDialog = (CGUIDialogNumeric *)g_windowManager.GetWindow(WINDOW_DIALOG_NUMERIC); pDialog->SetHeading( dlgHeading ); std::string strInput; if (!bVerifyInput) strInput = strToVerify; pDialog->SetMode(INPUT_PASSWORD, (void *)&strInput); pDialog->DoModal(); pDialog->GetOutput(&strInput); if (!pDialog->IsConfirmed() || pDialog->IsCanceled()) { // user canceled out strToVerify =""; return false; } std::string md5pword2 = XBMC::XBMC_MD5::GetMD5(strInput); if (!bVerifyInput) { strToVerify = md5pword2; StringUtils::ToLower(strToVerify); return true; } if (StringUtils::EqualsNoCase(strToVerify, md5pword2)) return true; // entered correct password // incorrect password return false; } bool CGUIDialogNumeric::IsConfirmed() const { return m_bConfirmed; } bool CGUIDialogNumeric::IsCanceled() const { return m_bCanceled; } void CGUIDialogNumeric::SetHeading(const std::string& strHeading) { Initialize(); CGUIMessage msg(GUI_MSG_LABEL_SET, GetID(), CONTROL_HEADING_LABEL); msg.SetLabel(strHeading); OnMessage(msg); } void CGUIDialogNumeric::VerifyDate(bool checkYear) { if (m_datetime.wDay == 0) m_datetime.wDay = 1; if (m_datetime.wMonth == 0) m_datetime.wMonth = 1; // check for number of days in the month if (m_datetime.wDay == 31) { if (m_datetime.wMonth == 4 || m_datetime.wMonth == 6 || m_datetime.wMonth == 9 || m_datetime.wMonth == 11) m_datetime.wDay = 30; } if (m_datetime.wMonth == 2 && m_datetime.wDay > 28) { m_datetime.wDay = 29; // max in february. if (checkYear) { // leap years occur when the year is divisible by 4 but not by 100, or the year is divisible by 400 // thus they don't occur, if the year has a remainder when divided by 4, or when the year is divisible by 100 but not by 400 if ( (m_datetime.wYear % 4) || ( !(m_datetime.wYear % 100) && (m_datetime.wYear % 400) ) ) m_datetime.wDay = 28; } } } void CGUIDialogNumeric::OnOK() { m_bConfirmed = true; m_bCanceled = false; Close(); } void CGUIDialogNumeric::OnCancel() { m_bConfirmed = false; m_bCanceled = true; Close(); }