a
554325746@qq.com
2019-12-31 fa104829ecef68865e5e3bce174515009c6d687b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
/*
 * 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 <http://www.gnu.org/licenses/>.
 */
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<String> androidKeys;
  public static Map<String, 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<String, Object> 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<String> getAndroidKeys() {
    return androidKeys;
  }
 
  public static Map<String, 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();
  }
}