| /* |
| Copyright 2012 to 2020 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| // input.cpp - GUIInput object |
| |
| #include <linux/input.h> |
| #include <pthread.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <sys/reboot.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <sys/ioctl.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| |
| #include <string> |
| |
| extern "C" { |
| #include "../twcommon.h" |
| } |
| #include "minuitwrp/minui.h" |
| #include "minuitwrp/truetype.hpp" |
| |
| #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; index<string_size; index++) |
| maskedValue += mMask; |
| displayValue = maskedValue; |
| } else { |
| displayValue = mValue; |
| } |
| |
| textWidth = twrpTruetype::gr_ttf_measureEx(displayValue.c_str(), fontResource); |
| } |
| |
| void GUIInput::HandleCursorByTouch(int x) { |
| // Uses x to find mCursorLocation and cursorX |
| if (displayValue.size() == 0) { |
| mCursorLocation = -1; |
| cursorX = mRenderX; |
| return; |
| } |
| |
| void* fontResource = NULL; |
| if (mFont) { |
| fontResource = mFont->GetResource(); |
| } 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 = twrpTruetype::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 = twrpTruetype::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; |
| } |