/* Copyright 2012 to 2016 bigbiff/Dees_Troy TeamWin This file is part of TWRP/TeamWin Recovery Project. TWRP 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 3 of the License, or (at your option) any later version. TWRP 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 TWRP. If not, see . */ // input.cpp - GUIInput object #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include "../twcommon.h" } #include "../minuitwrp/minui.h" #include "rapidxml.hpp" #include "objects.hpp" #include "../data.hpp" #define TW_INPUT_NO_UPDATE -1000 // Magic value for HandleTextLocation when no change in scrolling has occurred GUIInput::GUIInput(xml_node<>* node) : GUIObject(node) { xml_attribute<>* attr; xml_node<>* child; mInputText = NULL; mAction = NULL; mBackground = NULL; mCursor = NULL; mFont = NULL; mRendered = false; HasMask = false; DrawCursor = false; isLocalChange = true; HasAllowed = false; HasDisabled = false; cursorX = textWidth = scrollingX = mFontHeight = mFontY = lastX = 0; mBackgroundX = mBackgroundY = mBackgroundW = mBackgroundH = MinLen = MaxLen = 0; mCursorLocation = -1; // -1 is always the end of the string CursorWidth = 3; ConvertStrToColor("black", &mBackgroundColor); ConvertStrToColor("white", &mCursorColor); mValue = ""; displayValue = ""; if (!node) return; // Load text directly from the node mInputText = new GUIText(node); // Load action directly from the node mAction = new GUIAction(node); if (mInputText->Render() < 0) { delete mInputText; mInputText = NULL; } // Load the background child = FindNode(node, "background"); if (child) { mBackground = LoadAttrImage(child, "resource"); mBackgroundColor = LoadAttrColor(child, "color", mBackgroundColor); } if (mBackground && mBackground->GetResource()) { mBackgroundW = mBackground->GetWidth(); mBackgroundH = mBackground->GetHeight(); } // Load the cursor color child = FindNode(node, "cursor"); if (child) { mCursor = LoadAttrImage(child, "resource"); mCursorColor = LoadAttrColor(child, "color", mCursorColor); attr = child->first_attribute("hasfocus"); if (attr) { std::string focus = attr->value(); SetInputFocus(atoi(focus.c_str())); } CursorWidth = LoadAttrIntScaleX(child, "width", CursorWidth); } DrawCursor = HasInputFocus; // Load the font child = FindNode(node, "font"); if (child) { mFont = LoadAttrFont(child, "resource"); if (mFont && mFont->GetResource()) mFontHeight = mFont->GetHeight(); } child = FindNode(node, "data"); if (child) { attr = child->first_attribute("name"); if (attr) mVariable = attr->value(); attr = child->first_attribute("default"); if (attr) DataManager::SetValue(mVariable, attr->value()); mMask = LoadAttrString(child, "mask"); HasMask = !mMask.empty(); } // Load input restrictions child = FindNode(node, "restrict"); if (child) { MinLen = LoadAttrInt(child, "minlen", MinLen); MaxLen = LoadAttrInt(child, "maxlen", MaxLen); AllowedList = LoadAttrString(child, "allow"); HasAllowed = !AllowedList.empty(); DisabledList = LoadAttrString(child, "disable"); HasDisabled = !DisabledList.empty(); } // Load the placement LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH); SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH); if (mInputText && mFontHeight && mFontHeight < (unsigned)mRenderH) { mFontY = ((mRenderH - mFontHeight) / 2) + mRenderY; mInputText->SetRenderPos(mRenderX, mFontY); } else mFontY = mRenderY; if (mInputText) mInputText->SetMaxWidth(0); } GUIInput::~GUIInput() { delete mInputText; delete mAction; } void GUIInput::HandleTextLocation(int x) { mRendered = false; if (textWidth <= mRenderW) { if (x != TW_INPUT_NO_UPDATE) lastX = x; scrollingX = 0; return; } if (scrollingX + textWidth < mRenderW) { scrollingX = mRenderW - textWidth; } if (x == TW_INPUT_NO_UPDATE) return; scrollingX += x - lastX; if (scrollingX > 0) scrollingX = 0; else if (scrollingX + textWidth < mRenderW) scrollingX = mRenderW - textWidth; lastX = x; } void GUIInput::UpdateDisplayText() { void* fontResource = NULL; if (mFont) { fontResource = mFont->GetResource(); } else { textWidth = 0; return; } DataManager::GetValue(mVariable, mValue); if (HasMask) { int index, string_size = mValue.size(); string maskedValue; for (index=0; indexGetResource(); } else { return; } string cursorString; unsigned index = 0, displaySize = displayValue.size(); int prevX = mRenderX + scrollingX; for (index = 0; index <= displaySize; index++) { cursorString = displayValue.substr(0, index); cursorX = gr_ttf_measureEx(cursorString.c_str(), fontResource) + mRenderX + scrollingX; if (cursorX > x) { if (index > 0 && x <= cursorX - ((x - prevX) / 2) && prevX >= mRenderX) { // This helps make sure that we can place the cursor before the very first char if the first char is // is fully visible while also still letting us place the cursor after the last char if fully visible mCursorLocation = index - 1; cursorX = prevX; return; } mCursorLocation = index; if (cursorX > mRenderX + mRenderW) { cursorX = prevX; // This makes sure that the cursor doesn't get placed after the end of the input box mCursorLocation--; return; } if (cursorX >= mRenderX) { return; // This makes sure that the cursor doesn't get placed before the beginning of the input box } } prevX = cursorX; } mCursorLocation = -1; // x is at or past the end of the string } void GUIInput::HandleCursorByText() { // Uses mCursorLocation to find cursorX if (!DrawCursor) return; void* fontResource = NULL; if (mFont) { fontResource = mFont->GetResource(); } else { return; } int cursorTextWidth = textWidth; // width of text to the left of the cursor if (mCursorLocation != -1) { string cursorDisplay = displayValue; cursorDisplay.resize(mCursorLocation); cursorTextWidth = gr_ttf_measureEx(cursorDisplay.c_str(), fontResource); } cursorX = mRenderX + cursorTextWidth + scrollingX; if (cursorX >= mRenderX + mRenderW) { scrollingX = mRenderW - cursorTextWidth; cursorX = mRenderX + mRenderW - CursorWidth; } else if (cursorX < mRenderX) { scrollingX = cursorTextWidth * -1; cursorX = mRenderX; } } int GUIInput::Render(void) { if (!isConditionTrue()) { mRendered = false; return 0; } // First step, fill background gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, 255); gr_fill(mRenderX, mRenderY, mRenderW, mRenderH); // Next, render the background resource (if it exists) if (mBackground && mBackground->GetResource()) { mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2); mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2); gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY); } int ret = 0; // Render the text if (mInputText) { mInputText->SetRenderPos(mRenderX + scrollingX, mFontY); mInputText->SetText(displayValue); gr_clip(mRenderX, mRenderY, mRenderW, mRenderH); ret = mInputText->Render(); gr_noclip(); } if (ret < 0) return ret; if (HasInputFocus && DrawCursor) { // Render the cursor gr_color(mCursorColor.red, mCursorColor.green, mCursorColor.blue, 255); gr_fill(cursorX, mFontY, CursorWidth, mFontHeight); } mRendered = true; return ret; } int GUIInput::Update(void) { if (!isConditionTrue()) return (mRendered ? 2 : 0); if (!mRendered) return 2; int ret = 0; if (mInputText) ret = mInputText->Update(); if (ret < 0) return ret; return ret; } int GUIInput::GetSelection(int x, int y) { if (x < mRenderX || x - mRenderX > mRenderW || y < mRenderY || y - mRenderY > mRenderH) return -1; return (x - mRenderX); } int GUIInput::NotifyTouch(TOUCH_STATE state, int x, int y) { static int startSelection = -1; if (!isConditionTrue()) return -1; if (!HasInputFocus) { if (state != TOUCH_RELEASE) return 0; // Only change focus if touch releases within the input box if (GetSelection(x, y) >= 0) { // When changing focus, we don't scroll or change the cursor location PageManager::SetKeyBoardFocus(0); PageManager::NotifyCharInput(0); SetInputFocus(1); DrawCursor = true; mRendered = false; } } else { switch (state) { case TOUCH_HOLD: case TOUCH_REPEAT: break; case TOUCH_START: startSelection = GetSelection(x,y); lastX = x; DrawCursor = false; mRendered = false; break; case TOUCH_DRAG: // Check if we dragged out of the selection window if (GetSelection(x, y) == -1) { lastX = 0; break; } DrawCursor = false; // Provide some debounce on initial touches if (startSelection != -1 && abs(x - lastX) < 6) { break; } startSelection = -1; if (lastX != x) HandleTextLocation(x); break; case TOUCH_RELEASE: // We've moved the cursor location mRendered = false; DrawCursor = true; HandleCursorByTouch(x); break; } } return 0; } int GUIInput::NotifyVarChange(const std::string& varName, const std::string& value) { GUIObject::NotifyVarChange(varName, value); if (varName == mVariable) { if (!isLocalChange) { UpdateDisplayText(); HandleTextLocation(TW_INPUT_NO_UPDATE); } else isLocalChange = false; return 0; } if (varName.empty()) { UpdateDisplayText(); HandleTextLocation(TW_INPUT_NO_UPDATE); HandleCursorByText(); } return 0; } int GUIInput::NotifyKey(int key, bool down) { if (!HasInputFocus || !down) return 1; switch (key) { case KEY_LEFT: if (mCursorLocation == 0) return 0; // we're already at the beginning if (mCursorLocation == -1) { if (displayValue.size() == 0) { cursorX = mRenderX; return 0; } mCursorLocation = displayValue.size() - 1; } else { mCursorLocation--; } mRendered = false; HandleCursorByText(); return 0; case KEY_RIGHT: if (mCursorLocation == -1) return 0; // we're already at the end mCursorLocation++; if ((int)displayValue.size() <= mCursorLocation) mCursorLocation = -1; HandleCursorByText(); mRendered = false; return 0; case KEY_HOME: case KEY_UP: if (displayValue.size() == 0) return 0; mCursorLocation = 0; mRendered = false; cursorX = mRenderX; return 0; case KEY_END: case KEY_DOWN: mCursorLocation = -1; mRendered = false; HandleCursorByText(); return 0; } return 1; } int GUIInput::NotifyCharInput(int key) { if (HasInputFocus) { if (key == KEYBOARD_BACKSPACE) { //Backspace if (mValue.size() > 0 && mCursorLocation != 0) { if (mCursorLocation == -1) { mValue.resize(mValue.size() - 1); } else { mValue.erase(mCursorLocation - 1, 1); mCursorLocation--; } isLocalChange = true; DataManager::SetValue(mVariable, mValue); UpdateDisplayText(); HandleTextLocation(TW_INPUT_NO_UPDATE); HandleCursorByText(); } } else if (key == KEYBOARD_SWIPE_LEFT) { // Delete all isLocalChange = true; if (mCursorLocation == -1) { DataManager::SetValue (mVariable, ""); mValue = ""; textWidth = 0; mCursorLocation = -1; } else { mValue.erase(0, mCursorLocation); DataManager::SetValue(mVariable, mValue); mCursorLocation = 0; } UpdateDisplayText(); cursorX = mRenderX; scrollingX = 0; mRendered = false; return 0; } else if (key >= 32) { // Regular key if (HasAllowed && AllowedList.find((char)key) == string::npos) { return 0; } if (HasDisabled && DisabledList.find((char)key) != string::npos) { return 0; } if (MaxLen != 0 && mValue.size() >= MaxLen) { return 0; } if (mCursorLocation == -1) { mValue += key; } else { mValue.insert(mCursorLocation, 1, key); mCursorLocation++; } isLocalChange = true; DataManager::SetValue(mVariable, mValue); UpdateDisplayText(); HandleTextLocation(TW_INPUT_NO_UPDATE); HandleCursorByText(); } else if (key == KEYBOARD_ACTION) { // Action if (mAction) { unsigned inputLen = mValue.length(); if (inputLen < MinLen) return 0; else if (MaxLen != 0 && inputLen > MaxLen) return 0; else return (mAction ? mAction->NotifyTouch(TOUCH_RELEASE, mRenderX, mRenderY) : 1); } } return 0; } else { if (key == 0) { // Somewhat ugly hack-ish way to tell the box to redraw after losing focus to remove the cursor mRendered = false; return 1; } } return 1; }