diff options
Diffstat (limited to 'gui/keyboard.cpp')
-rw-r--r-- | gui/keyboard.cpp | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/gui/keyboard.cpp b/gui/keyboard.cpp new file mode 100644 index 000000000..849cf19d8 --- /dev/null +++ b/gui/keyboard.cpp @@ -0,0 +1,633 @@ +/* + Copyright 2012 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 <http://www.gnu.org/licenses/>. +*/ + +#include <linux/input.h> +#include <stdlib.h> +#include <string.h> +#include "../data.hpp" + +#include <string> + +extern "C" { +#include "../twcommon.h" +#include "gui.h" +} +#include "../minuitwrp/minui.h" + +#include "rapidxml.hpp" +#include "objects.hpp" + +bool GUIKeyboard::CtrlActive = false; + +GUIKeyboard::GUIKeyboard(xml_node<>* node) + : GUIObject(node) +{ + int layoutindex, rowindex, keyindex, Xindex, Yindex, keyHeight = 0, keyWidth = 0; + currentKey = NULL; + highlightRenderCount = 0; + hasHighlight = hasCapsHighlight = hasCtrlHighlight = false; + char resource[10], layout[8], row[5], key[6], longpress[7]; + xml_attribute<>* attr; + xml_node<>* child; + xml_node<>* keylayout; + xml_node<>* keyrow; + + for (layoutindex=0; layoutindex<MAX_KEYBOARD_LAYOUTS; layoutindex++) { + layouts[layoutindex].keyboardImg = NULL; + memset(layouts[layoutindex].keys, 0, sizeof(Layout::keys)); + memset(layouts[layoutindex].row_end_y, 0, sizeof(Layout::row_end_y)); + } + + mRendered = false; + currentLayout = 1; + CapsLockOn = false; + + if (!node) return; + + mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlight); + mCapsHighlightColor = LoadAttrColor(FindNode(node, "capshighlight"), "color", &hasCapsHighlight); + mCtrlHighlightColor = LoadAttrColor(FindNode(node, "ctrlhighlight"), "color", &hasCtrlHighlight); + + child = FindNode(node, "keymargin"); + mKeyMarginX = LoadAttrIntScaleX(child, "x", 0); + mKeyMarginY = LoadAttrIntScaleY(child, "y", 0); + + child = FindNode(node, "background"); + mBackgroundColor = LoadAttrColor(child, "color", COLOR(32,32,32,255)); + + child = FindNode(node, "key-alphanumeric"); + mFont = PageManager::GetResources()->FindFont(LoadAttrString(child, "font", "keylabel")); + mFontColor = LoadAttrColor(child, "textcolor", COLOR(255,255,255,255)); + mKeyColorAlphanumeric = LoadAttrColor(child, "color", COLOR(0,0,0,0)); + + child = FindNode(node, "key-other"); + mSmallFont = PageManager::GetResources()->FindFont(LoadAttrString(child, "font", "keylabel-small")); + mFontColorSmall = LoadAttrColor(child, "textcolor", COLOR(192,192,192,255)); + mKeyColorOther = LoadAttrColor(child, "color", COLOR(0,0,0,0)); + + child = FindNode(node, "longpress"); + mLongpressFont = PageManager::GetResources()->FindFont(LoadAttrString(child, "font", "keylabel-longpress")); + mLongpressFontColor = LoadAttrColor(child, "textcolor", COLOR(128,128,128,255)); + LoadPlacement(child, &longpressOffsetX, &longpressOffsetY); + + LoadKeyLabels(node, 0); // load global key labels + + // compatibility ugliness: resources should be specified in the layouts themselves instead + // Load the images for the different layouts + child = FindNode(node, "layout"); + if (child) + { + layoutindex = 1; + strcpy(resource, "resource1"); + attr = child->first_attribute(resource); + while (attr && layoutindex < (MAX_KEYBOARD_LAYOUTS + 1)) { + layouts[layoutindex - 1].keyboardImg = LoadAttrImage(child, resource); + + layoutindex++; + resource[8] = (char)(layoutindex + 48); + attr = child->first_attribute(resource); + } + } + + // Check the first image to get height and width + if (layouts[0].keyboardImg && layouts[0].keyboardImg->GetResource()) + { + mRenderW = layouts[0].keyboardImg->GetWidth(); + mRenderH = layouts[0].keyboardImg->GetHeight(); + } + + // Load all of the layout maps + layoutindex = 1; + strcpy(layout, "layout1"); + keylayout = FindNode(node, layout); + while (keylayout) + { + if (layoutindex > MAX_KEYBOARD_LAYOUTS) { + LOGERR("Too many layouts defined in keyboard.\n"); + return; + } + + LoadKeyLabels(keylayout, layoutindex); // load per-layout key labels + + Layout& lay = layouts[layoutindex - 1]; + + child = keylayout->first_node("keysize"); + keyHeight = LoadAttrIntScaleY(child, "height", 0); + keyWidth = LoadAttrIntScaleX(child, "width", 0); + // compatibility ugliness: capslock="0" means that this is the caps layout. Also it has nothing to do with keysize. + lay.is_caps = (LoadAttrInt(child, "capslock", 1) == 0); + // compatibility ugliness: revert_layout has nothing to do with keysize. + lay.revert_layout = LoadAttrInt(child, "revert_layout", -1); + + rowindex = 1; + Yindex = 0; + strcpy(row, "row1"); + keyrow = keylayout->first_node(row); + while (keyrow) + { + if (rowindex > MAX_KEYBOARD_ROWS) { + LOGERR("Too many rows defined in keyboard.\n"); + return; + } + + Yindex += keyHeight; + lay.row_end_y[rowindex - 1] = Yindex; + + keyindex = 1; + Xindex = 0; + strcpy(key, "key01"); + attr = keyrow->first_attribute(key); + + while (attr) { + if (keyindex > MAX_KEYBOARD_KEYS) { + LOGERR("Too many keys defined in a keyboard row.\n"); + return; + } + + const char* keyinfo = attr->value(); + + if (strlen(keyinfo) == 0) { + LOGERR("No key info on layout%i, row%i, key%dd.\n", layoutindex, rowindex, keyindex); + return; + } + + if (ParseKey(keyinfo, lay.keys[rowindex - 1][keyindex - 1], Xindex, keyWidth, false)) + LOGERR("Invalid key info on layout%i, row%i, key%02i.\n", layoutindex, rowindex, keyindex); + + + // PROCESS LONG PRESS INFO IF EXISTS + sprintf(longpress, "long%02i", keyindex); + attr = keyrow->first_attribute(longpress); + if (attr) { + const char* keyinfo = attr->value(); + + if (strlen(keyinfo) == 0) { + LOGERR("No long press info on layout%i, row%i, long%dd.\n", layoutindex, rowindex, keyindex); + return; + } + + if (ParseKey(keyinfo, lay.keys[rowindex - 1][keyindex - 1], Xindex, keyWidth, true)) + LOGERR("Invalid long press key info on layout%i, row%i, long%02i.\n", layoutindex, rowindex, keyindex); + } + keyindex++; + sprintf(key, "key%02i", keyindex); + attr = keyrow->first_attribute(key); + } + rowindex++; + row[3] = (char)(rowindex + 48); + keyrow = keylayout->first_node(row); + } + layoutindex++; + layout[6] = (char)(layoutindex + 48); + keylayout = FindNode(node, layout); + } + + int x, y; + // Load the placement + LoadPlacement(FindNode(node, "placement"), &x, &y, &mRenderW, &mRenderH); + SetRenderPos(x, y, mRenderW, mRenderH); + return; +} + +GUIKeyboard::~GUIKeyboard() +{ +} + +int GUIKeyboard::ParseKey(const char* keyinfo, Key& key, int& Xindex, int keyWidth, bool longpress) +{ + key.layout = 0; + int keychar = 0; + if (strlen(keyinfo) == 1) { + // This is a single key, simple definition + keychar = keyinfo[0]; + } else { + // This key has extra data: {keywidth}:{what_the_key_does} + keyWidth = scale_theme_x(atoi(keyinfo)); + + const char* ptr = keyinfo; + while (*ptr > 32 && *ptr != ':') + ptr++; + if (*ptr != ':') + return -1; // no colon is an error + ptr++; + + if (*ptr == 0) { // This is an empty area + keychar = 0; + } else if (strlen(ptr) == 1) { // This is the character that this key uses + keychar = *ptr; + } else if (*ptr == 'c') { // This is an ASCII character code: "c:{number}" + keychar = atoi(ptr + 2); + } else if (*ptr == 'k') { // This is a Linux keycode from input.h: "k:{number}" + keychar = -atoi(ptr + 2); + } else if (*ptr == 'l') { // This is a different layout: "layout{number}" + key.layout = atoi(ptr + 6); + } else if (*ptr == 'a') { // This is an action: "action" (the Enter key) + keychar = KEYBOARD_ACTION; + } else + return -1; + } + + if (longpress) { + key.longpresskey = keychar; + } else { + key.key = keychar; + Xindex += keyWidth; + key.end_x = Xindex - 1; + } + + return 0; +} + +void GUIKeyboard::LoadKeyLabels(xml_node<>* parent, int layout) +{ + for (xml_node<>* child = parent->first_node(); child; child = child->next_sibling()) { + std::string name = child->name(); + if (name == "keylabel") { + std::string keydef = LoadAttrString(child, "key", ""); + Key tempkey; + int dummyX; + if (ParseKey(keydef.c_str(), tempkey, dummyX, 0, false) == 0) { + KeyLabel keylabel; + keylabel.key = tempkey.key; + keylabel.layout_from = layout; + keylabel.layout_to = tempkey.layout; + keylabel.text = LoadAttrString(child, "text", ""); + keylabel.image = LoadAttrImage(child, "resource"); + mKeyLabels.push_back(keylabel); + } else { + LOGERR("Ignoring invalid keylabel in layout %d: '%s'.\n", layout, keydef.c_str()); + } + } + } +} + +void GUIKeyboard::DrawKey(Key& key, int keyX, int keyY, int keyW, int keyH) +{ + int keychar = key.key; + if (!keychar && !key.layout) + return; + + // key background + COLOR& c = (keychar >= 32 && keychar < 127) ? mKeyColorAlphanumeric : mKeyColorOther; + gr_color(c.red, c.green, c.blue, c.alpha); + keyX += mKeyMarginX; + keyY += mKeyMarginY; + keyW -= mKeyMarginX * 2; + keyH -= mKeyMarginY * 2; + gr_fill(keyX, keyY, keyW, keyH); + + // key label + FontResource* labelFont = mFont; + string labelText; + ImageResource* labelImage = NULL; + if (keychar > 32 && keychar < 127) { + // TODO: this will eventually need UTF-8 support + labelText = (char) keychar; + if (CtrlActive) { + int ctrlchar = KeyCharToCtrlChar(keychar); + if (ctrlchar != keychar) + labelText = std::string("^") + (char)(ctrlchar + 64); + } + gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha); + } + else { + // search for a special key label + for (std::vector<KeyLabel>::iterator it = mKeyLabels.begin(); it != mKeyLabels.end(); ++it) { + if (it->layout_from > 0 && it->layout_from != currentLayout) + continue; // this label is for another layout + if (it->key == key.key && it->layout_to == key.layout) + { + // found a label + labelText = it->text; + labelImage = it->image; + break; + } + } + labelFont = mSmallFont; + gr_color(mFontColorSmall.red, mFontColorSmall.green, mFontColorSmall.blue, mFontColorSmall.alpha); + } + + if (labelImage) + { + int w = labelImage->GetWidth(); + int h = labelImage->GetHeight(); + int x = keyX + (keyW - w) / 2; + int y = keyY + (keyH - h) / 2; + gr_blit(labelImage->GetResource(), 0, 0, w, h, x, y); + } + else if (!labelText.empty()) + { + void* fontResource = labelFont->GetResource(); + int textW = gr_ttf_measureEx(labelText.c_str(), fontResource); + int textH = labelFont->GetHeight(); + int textX = keyX + (keyW - textW) / 2; + int textY = keyY + (keyH - textH) / 2; + gr_textEx_scaleW(textX, textY, labelText.c_str(), fontResource, keyW, TOP_LEFT, 0); + } + + // longpress key label (only if font is defined) + keychar = key.longpresskey; + if (keychar > 32 && keychar < 127 && mLongpressFont->GetResource()) { + void* fontResource = mLongpressFont->GetResource(); + gr_color(mLongpressFontColor.red, mLongpressFontColor.green, mLongpressFontColor.blue, mLongpressFontColor.alpha); + string text(1, keychar); + int textW = gr_ttf_measureEx(text.c_str(), fontResource); + int textX = keyX + keyW - longpressOffsetX - textW; + int textY = keyY + longpressOffsetY; + gr_textEx_scaleW(textX, textY, text.c_str(), fontResource, keyW, TOP_LEFT, 0); + } +} + +int GUIKeyboard::KeyCharToCtrlChar(int key) +{ + // convert upper and lower case to ctrl chars + // Ctrl+A to Ctrl+_ (we don't support entering null bytes) + if (key >= 65 && key <= 127 && key != 96) + return key & 0x1f; + return key; // pass on others (already ctrl chars, numbers, etc.) unchanged +} + +int GUIKeyboard::Render(void) +{ + if (!isConditionTrue()) + { + mRendered = false; + return 0; + } + + Layout& lay = layouts[currentLayout - 1]; + + bool drawKeys = false; + if (lay.keyboardImg && lay.keyboardImg->GetResource()) + // keyboard is image based + gr_blit(lay.keyboardImg->GetResource(), 0, 0, mRenderW, mRenderH, mRenderX, mRenderY); + else { + // keyboard is software drawn + // fill background + gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha); + gr_fill(mRenderX, mRenderY, mRenderW, mRenderH); + drawKeys = true; + } + + // draw keys + int y1 = 0; + for (int row = 0; row < MAX_KEYBOARD_ROWS; ++row) { + int rowY = mRenderY + y1; + int rowH = lay.row_end_y[row] - y1; + y1 = lay.row_end_y[row]; + int x1 = 0; + for (int col = 0; col < MAX_KEYBOARD_KEYS; ++col) { + Key& key = lay.keys[row][col]; + int keyY = rowY; + int keyH = rowH; + int keyX = mRenderX + x1; + int keyW = key.end_x - x1; + x1 = key.end_x; + + // Draw key for software drawn keyboard + if (drawKeys) + DrawKey(key, keyX, keyY, keyW, keyH); + + // Draw highlight for capslock + if (hasCapsHighlight && lay.is_caps && CapsLockOn && key.layout > 0 && key.layout == lay.revert_layout) { + gr_color(mCapsHighlightColor.red, mCapsHighlightColor.green, mCapsHighlightColor.blue, mCapsHighlightColor.alpha); + gr_fill(keyX, keyY, keyW, keyH); + } + + // Draw highlight for control + if (hasCtrlHighlight && key.key == -KEY_LEFTCTRL && CtrlActive) { + gr_color(mCtrlHighlightColor.red, mCtrlHighlightColor.green, mCtrlHighlightColor.blue, mCtrlHighlightColor.alpha); + gr_fill(keyX, keyY, keyW, keyH); + } + + // Highlight current key + if (hasHighlight && &key == currentKey && highlightRenderCount != 0) { + gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha); + gr_fill(keyX, keyY, keyW, keyH); + } + } + } + + if (!hasHighlight || highlightRenderCount == 0) + mRendered = true; + else if (highlightRenderCount > 0) + highlightRenderCount--; + return 0; +} + +int GUIKeyboard::Update(void) +{ + if (!isConditionTrue()) return (mRendered ? 2 : 0); + if (!mRendered) return 2; + + return 0; +} + +int GUIKeyboard::SetRenderPos(int x, int y, int w, int h) +{ + RenderObject::SetRenderPos(x, y, w, h); + SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH); + return 0; +} + +GUIKeyboard::Key* GUIKeyboard::HitTestKey(int x, int y) +{ + if (!IsInRegion(x, y)) + return NULL; + + int rely = y - mRenderY; + int relx = x - mRenderX; + + Layout& lay = layouts[currentLayout - 1]; + + // Find the correct row + int row; + for (row = 0; row < MAX_KEYBOARD_ROWS; ++row) { + if (lay.row_end_y[row] > rely) + break; + } + if (row == MAX_KEYBOARD_ROWS) + return NULL; + + // Find the correct key (column) + int col; + int x1 = 0; + for (col = 0; col < MAX_KEYBOARD_KEYS; ++col) { + Key& key = lay.keys[row][col]; + if (x1 <= relx && relx < key.end_x && (key.key != 0 || key.layout != 0)) { + // This is the key that was pressed! + return &key; + } + x1 = key.end_x; + } + return NULL; +} + +int GUIKeyboard::NotifyTouch(TOUCH_STATE state, int x, int y) +{ + static int was_held = 0, startX = 0; + + if (!isConditionTrue()) return -1; + + switch (state) + { + case TOUCH_START: + was_held = 0; + startX = x; + currentKey = HitTestKey(x, y); + if (currentKey) + highlightRenderCount = -1; + else + highlightRenderCount = 0; + mRendered = false; + break; + + case TOUCH_DRAG: + break; + + case TOUCH_RELEASE: + // TODO: we might want to notify of key releases here + if (x < startX - (mRenderW * 0.5)) { + if (highlightRenderCount != 0) { + highlightRenderCount = 0; + mRendered = false; + } + PageManager::NotifyCharInput(KEYBOARD_SWIPE_LEFT); + return 0; + } else if (x > startX + (mRenderW * 0.5)) { + if (highlightRenderCount != 0) { + highlightRenderCount = 0; + mRendered = false; + } + PageManager::NotifyCharInput(KEYBOARD_SWIPE_RIGHT); + return 0; + } + // fall through + case TOUCH_HOLD: + case TOUCH_REPEAT: + if (!currentKey) { + if (highlightRenderCount != 0) { + highlightRenderCount = 0; + mRendered = false; + } + return 0; + } + + if (highlightRenderCount != 0) { + if (state == TOUCH_RELEASE) + highlightRenderCount = 2; + else + highlightRenderCount = -1; + mRendered = false; + } + + if (HitTestKey(x, y) != currentKey) { + // We dragged off of the starting key + currentKey = NULL; + if (highlightRenderCount != 0) { + highlightRenderCount = 0; + mRendered = false; + } + return 0; + } else { + Key& key = *currentKey; + bool repeatKey = false; + Layout& lay = layouts[currentLayout - 1]; + if (state == TOUCH_RELEASE && was_held == 0) { + DataManager::Vibrate("tw_keyboard_vibrate"); + if (key.layout > 0) { + // Switch layouts + if (lay.is_caps && key.layout == lay.revert_layout && !CapsLockOn) { + CapsLockOn = true; // Set the caps lock + } else { + CapsLockOn = false; // Unset the caps lock and change layouts + currentLayout = key.layout; + } + mRendered = false; + } else if (key.key == KEYBOARD_ACTION) { + // Action + highlightRenderCount = 0; + // Send action notification + PageManager::NotifyCharInput(key.key); + } else if (key.key == -KEY_LEFTCTRL) { + CtrlActive = !CtrlActive; // toggle Control key state + mRendered = false; // render Ctrl key highlight + } else if (key.key != 0) { + // Regular key + if (key.key > 0) { + // ASCII code or character + int keycode = key.key; + if (CtrlActive) { + CtrlActive = false; + mRendered = false; + keycode = KeyCharToCtrlChar(key.key); + } + PageManager::NotifyCharInput(keycode); + } else { + // Linux key code + PageManager::NotifyKey(-key.key, true); + PageManager::NotifyKey(-key.key, false); + } + if (!CapsLockOn && lay.is_caps) { + // caps lock was not set, change layouts + currentLayout = lay.revert_layout; + mRendered = false; + } + } + } else if (state == TOUCH_HOLD) { + was_held = 1; + if (key.longpresskey > 0) { + // Long Press Key + DataManager::Vibrate("tw_keyboard_vibrate"); + PageManager::NotifyCharInput(key.longpresskey); + } + else + repeatKey = true; + } else if (state == TOUCH_REPEAT) { + was_held = 1; + repeatKey = true; + } + if (repeatKey) { + if (key.key == KEYBOARD_BACKSPACE) { + // Repeat backspace + PageManager::NotifyCharInput(key.key); + } + switch (key.key) + { + // Repeat arrows + case -KEY_LEFT: + case -KEY_RIGHT: + case -KEY_UP: + case -KEY_DOWN: + PageManager::NotifyKey(-key.key, true); + PageManager::NotifyKey(-key.key, false); + break; + } + } + } + break; + } + + return 0; +} + +void GUIKeyboard::SetPageFocus(int inFocus) +{ + if (inFocus) + CtrlActive = false; +} |