/* * Copyright (C) 2015-present, osfans * waxaca@163.com https://github.com/osfans * * 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 3 of the License, 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 this program. If not, see . */ package com.osfans.trime; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.view.KeyCharacterMap; import android.view.KeyEvent; import com.basic.security.utils.FrameUtil; import com.osfans.trime.enums.KeyEventType; import java.util.List; import java.util.Map; /** {@link Keyboard 鍵盤}中的各個按鍵,包含單擊、長按、滑動等多種{@link Event 事件} */ public class Key { public static final int[] KEY_STATE_NORMAL_ON = { android.R.attr.state_checkable, android.R.attr.state_checked }; public static final int[] KEY_STATE_PRESSED_ON = { android.R.attr.state_pressed, android.R.attr.state_checkable, android.R.attr.state_checked }; public static final int[] KEY_STATE_NORMAL_OFF = {android.R.attr.state_checkable}; public static final int[] KEY_STATE_PRESSED_OFF = { android.R.attr.state_pressed, android.R.attr.state_checkable }; public static final int[] KEY_STATE_NORMAL = {}; public static final int[] KEY_STATE_PRESSED = {android.R.attr.state_pressed}; public static final int[][] KEY_STATES = new int[][] { KEY_STATE_PRESSED_ON, KEY_STATE_PRESSED_OFF, KEY_STATE_NORMAL_ON, KEY_STATE_NORMAL_OFF, KEY_STATE_PRESSED, KEY_STATE_NORMAL }; public static List androidKeys; public static Map presetKeys; private static final int EVENT_NUM = KeyEventType.values().length; public Event[] events = new Event[EVENT_NUM]; public int edgeFlags; private static int symbolStart; private static String symbols; private static KeyCharacterMap kcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD); private Keyboard mKeyboard; private Event ascii; private Event composing; private Event has_menu; private Event paging; private boolean send_bindings = true; private int width; private int height; private int gap; private int row; private int column; private String label; private String hint; private Drawable key_back_color; private Drawable hilited_key_back_color; private Integer key_text_color; private Integer key_symbol_color; private Integer hilited_key_text_color; private Integer hilited_key_symbol_color; private Integer key_text_size; private Integer symbol_text_size; private Float round_corner; private int key_text_offset_x; private int key_text_offset_y; private int key_symbol_offset_x; private int key_symbol_offset_y; private int key_hint_offset_x; private int key_hint_offset_y; private int key_press_offset_x; private int key_press_offset_y; private int x; private int y; private boolean pressed; private boolean on; private String popupCharacters; private int popupResId; /** * Create an empty key with no attributes. * * @param parent 按鍵所在的{@link Keyboard 鍵盤} */ public Key(Keyboard parent) { mKeyboard = parent; } /** * Create an empty key with no attributes. * * @param parent 按鍵所在的{@link Keyboard 鍵盤} * @param mk 從YAML中解析得到的Map */ public Key(Keyboard parent, Map mk) { this(parent); String s; for (int i = 0; i < EVENT_NUM; i++) { String[] eventTypes = new String[] { "click", "long_click", "swipe_left", "swipe_right", "swipe_up", "swipe_down", "combo" }; String eventType = eventTypes[i]; s = Config.getString(mk, eventType); if (!Function.isEmpty(s)) events[i] = new Event(mKeyboard, s); else if (i == KeyEventType.CLICK.ordinal()) events[i] = new Event(mKeyboard, ""); } s = Config.getString(mk, "composing"); if (!Function.isEmpty(s)) composing = new Event(mKeyboard, s); s = Config.getString(mk, "has_menu"); if (!Function.isEmpty(s)) has_menu = new Event(mKeyboard, s); s = Config.getString(mk, "paging"); if (!Function.isEmpty(s)) paging = new Event(mKeyboard, s); if (composing != null || has_menu != null || paging != null) mKeyboard.getmComposingKeys().add(this); s = Config.getString(mk, "ascii"); if (!Function.isEmpty(s)) ascii = new Event(mKeyboard, s); label = Config.getString(mk, "label"); hint = Config.getString(mk, "hint"); if (mk.containsKey("send_bindings")) send_bindings = Config.getBoolean(mk, "send_bindings"); else if (composing == null && has_menu == null && paging == null) send_bindings = false; if (isShift()) mKeyboard.setmShiftKey(this); key_text_size = Config.getPixel(mk, "key_text_size"); symbol_text_size = Config.getPixel(mk, "symbol_text_size"); key_text_color = Color.parseColor("#4bd2ff");//Config.getColor(mk, "key_text_color"); hilited_key_text_color = Config.getColor(mk, "hilited_key_text_color"); key_back_color = Config.getColorDrawable(mk, "key_back_color"); hilited_key_back_color = Config.getColorDrawable(mk, "hilited_key_back_color"); key_symbol_color = Config.getColor(mk, "key_symbol_color"); hilited_key_symbol_color = Config.getColor(mk, "hilited_key_symbol_color"); round_corner = 30.0f;//Config.getFloat(mk, "round_corner"); } public static List getAndroidKeys() { return androidKeys; } public static Map getPresetKeys() { return presetKeys; } public static int getSymbolStart() { return symbolStart; } public static void setSymbolStart(int symbolStart) { Key.symbolStart = symbolStart; } public static String getSymbols() { return symbols; } public static void setSymbols(String symbols) { Key.symbols = symbols; } public static KeyCharacterMap getKcm() { return kcm; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getGap() { return gap; } public void setGap(int gap) { this.gap = gap; } public int getEdgeFlags() { return edgeFlags; } public void setEdgeFlags(int edgeFlags) { this.edgeFlags = edgeFlags; } public int getRow() { return row; } public void setRow(int row) { this.row = row; } public int getColumn() { return column; } public void setColumn(int column) { this.column = column; } public String getHint() { return hint; } public Integer getKey_text_size() { return key_text_size; } public Integer getSymbol_text_size() { return symbol_text_size; } public Float getRound_corner() { return round_corner; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public boolean isPressed() { return pressed; } public boolean isOn() { return on; } public void setOn(boolean on) { this.on = on; } public String getPopupCharacters() { return popupCharacters; } public int getPopupResId() { return popupResId; } public int getKey_text_offset_x() { return key_text_offset_x + getKey_offset_x(); } public void setKey_text_offset_x(int key_text_offset_x) { this.key_text_offset_x = key_text_offset_x; } public int getKey_text_offset_y() { return key_text_offset_y + getKey_offset_y(); } public void setKey_text_offset_y(int key_text_offset_y) { this.key_text_offset_y = key_text_offset_y; } public int getKey_symbol_offset_x() { return key_symbol_offset_x + getKey_offset_x(); } public void setKey_symbol_offset_x(int key_symbol_offset_x) { this.key_symbol_offset_x = key_symbol_offset_x; } public int getKey_symbol_offset_y() { return key_symbol_offset_y + getKey_offset_y(); } public void setKey_symbol_offset_y(int key_symbol_offset_y) { this.key_symbol_offset_y = key_symbol_offset_y; } public int getKey_hint_offset_x() { return key_hint_offset_x + getKey_offset_x(); } public void setKey_hint_offset_x(int key_hint_offset_x) { this.key_hint_offset_x = key_hint_offset_x; } public int getKey_hint_offset_y() { return key_hint_offset_y + getKey_offset_y(); } public void setKey_hint_offset_y(int key_hint_offset_y) { this.key_hint_offset_y = key_hint_offset_y; } public void setKey_press_offset_x(int key_press_offset_x) { this.key_press_offset_x = key_press_offset_x; } public void setKey_press_offset_y(int key_press_offset_y) { this.key_press_offset_y = key_press_offset_y; } public int getKey_offset_x() { return pressed ? key_press_offset_x : 0; } public int getKey_offset_y() { return pressed ? key_press_offset_y : 0; } private boolean isNormal(int[] drawableState) { return (drawableState == KEY_STATE_NORMAL || drawableState == KEY_STATE_NORMAL_ON || drawableState == KEY_STATE_NORMAL_OFF); } public Drawable getBackColorForState(int[] drawableState) { if (isNormal(drawableState)) return key_back_color; else return hilited_key_back_color; } public Integer getTextColorForState(int[] drawableState) { if (isNormal(drawableState)) return key_text_color; else return hilited_key_text_color; } public Integer getSymbolColorForState(int[] drawableState) { if (isNormal(drawableState)) return key_symbol_color; else return hilited_key_symbol_color; } /** * Informs the key that it has been pressed, in case it needs to change its appearance or state. * * @see #onReleased(boolean) */ public void onPressed() { pressed = !pressed; } /** * Changes the pressed state of the key. If it is a sticky key, it will also change the toggled * state of the key if the finger was release inside. * * @param inside whether the finger was released inside the key * @see #onPressed() */ public void onReleased(boolean inside) { pressed = !pressed; if (getClick().isSticky()) on = !on; } /** * Detects if a point falls inside this key. * * @param x the x-coordinate of the point * @param y the y-coordinate of the point * @return whether or not the point falls inside the key. If the key is attached to an edge, it * will assume that all points between the key and the edge are considered to be inside the * key. */ public boolean isInside(int x, int y) { boolean leftEdge = (edgeFlags & Keyboard.EDGE_LEFT) > 0; boolean rightEdge = (edgeFlags & Keyboard.EDGE_RIGHT) > 0; boolean topEdge = (edgeFlags & Keyboard.EDGE_TOP) > 0; boolean bottomEdge = (edgeFlags & Keyboard.EDGE_BOTTOM) > 0; if ((x >= this.x || (leftEdge && x <= this.x + this.width)) && (x < this.x + this.width || (rightEdge && x >= this.x)) && (y >= this.y || (topEdge && y <= this.y + this.height)) && (y < this.y + this.height || (bottomEdge && y >= this.y))) { return true; } else { return false; } } /** * Returns the square of the distance between the center of the key and the given point. * * @param x the x-coordinate of the point * @param y the y-coordinate of the point * @return the square of the distance of the point from the center of the key */ public int squaredDistanceFrom(int x, int y) { int xDist = this.x + width / 2 - x; int yDist = this.y + height / 2 - y; return xDist * xDist + yDist * yDist; } /** * Returns the drawable state for the key, based on the current state and type of the key. * * @return the drawable state of the key. * @see android.graphics.drawable.StateListDrawable#setState(int[]) */ public int[] getCurrentDrawableState() { int[] states = KEY_STATE_NORMAL; boolean isShifted = isShift() && mKeyboard.isShifted(); //臨時大寫 if (isShifted || on) { if (pressed) { states = KEY_STATE_PRESSED_ON; } else { states = KEY_STATE_NORMAL_ON; } } else { if (getClick().isSticky() || getClick().isFunctional()) { if (pressed) { states = KEY_STATE_PRESSED_OFF; } else { states = KEY_STATE_NORMAL_OFF; } } else { if (pressed) { states = KEY_STATE_PRESSED; } } } return states; } public boolean isShift() { int c = getCode(); return (c == KeyEvent.KEYCODE_SHIFT_LEFT || c == KeyEvent.KEYCODE_SHIFT_RIGHT); } public boolean isShiftLock() { switch (getClick().getShiftLock()) { case "long": return false; case "click": return true; } return !Rime.isAsciiMode(); } public boolean sendBindings(int type) { Event e = null; if (type > 0 && type <= EVENT_NUM) e = events[type]; if (e != null) return true; if (ascii != null && Rime.isAsciiMode()) return false; if (send_bindings) { if (paging != null && Rime.isPaging()) return true; if (has_menu != null && Rime.hasMenu()) return true; if (composing != null && Rime.isComposing()) return true; } return false; } private Event getEvent() { if (ascii != null && Rime.isAsciiMode()) return ascii; if (paging != null && Rime.isPaging()) return paging; if (has_menu != null && Rime.hasMenu()) return has_menu; if (composing != null && Rime.isComposing()) return composing; return getClick(); } public Event getClick() { return events[KeyEventType.CLICK.ordinal()]; } public Event getLongClick() { return events[KeyEventType.LONG_CLICK.ordinal()]; } public boolean hasEvent(int i) { return events[i] != null; } public Event getEvent(int i) { Event e = null; if (i > 0 && i <= EVENT_NUM) e = events[i]; if (e != null) return e; if (ascii != null && Rime.isAsciiMode()) return ascii; if (send_bindings) { if (paging != null && Rime.isPaging()) return paging; if (has_menu != null && Rime.hasMenu()) return has_menu; if (composing != null && Rime.isComposing()) return composing; } return getClick(); } public int getCode() { return getClick().getCode(); } public int getCode(int type) { return getEvent(type).getCode(); } public String getLabel() { Event event = getEvent(); if (!Function.isEmpty(label) && event == getClick() && (ascii == null && !Rime.isAsciiMode())) return label; //中文狀態顯示標籤 return event.getLabel(); } public String getPreviewText(int type) { if (type == KeyEventType.CLICK.ordinal()) return getEvent().getPreviewText(); return getEvent(type).getPreviewText(); } public String getSymbolLabel() { return getLongClick().getLabel(); } }