package me.sergiotarxz.openmg.x11; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Rect; import android.preference.PreferenceManager; import androidx.appcompat.widget.AppCompatTextView; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.widget.HorizontalScrollView; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.view.SurfaceView; import java.util.HashMap; import java.util.Map; @SuppressWarnings({"unused", "FieldCanBeLocal"}) public class AdditionalKeyboardView extends HorizontalScrollView implements ViewTreeObserver.OnGlobalLayoutListener { private final static int KEYCODE_BASE = 300; public final static int PREFERENCES_KEY = KEYCODE_BASE + 1; public final static int KEY_HEIGHT_DP = 35; private boolean softKbdVisible; private Context ctx; private View targetView = null; private View.OnKeyListener targetListener = null; private int density; private LinearLayout root; public AdditionalKeyboardView(Context context) { super(context); init(context); } public AdditionalKeyboardView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } @SuppressLint("SetTextI18n") private void init(Context context) { ctx = context; density = (int) context.getResources().getDisplayMetrics().density; getViewTreeObserver().addOnGlobalLayoutListener(this); setBackgroundColor(0xFF000000); LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, KEY_HEIGHT_DP * density); root = new LinearLayout(context); root.setLayoutParams(lp); root.setOrientation(LinearLayout.HORIZONTAL); addView(root); } @Override public void onGlobalLayout() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx); if (!preferences.getBoolean("showAdditionalKbd", true)) { if (getVisibility() != View.GONE) setVisibility(View.GONE); return; } else { int visibility = View.VISIBLE; softKbdVisible = (visibility == View.VISIBLE); if (!softKbdVisible) setVisibility(visibility); } } public void reload(int[] keys, View TargetView, View.OnKeyListener TargetListener) { targetView = TargetView; targetListener = TargetListener; root.removeAllViews(); LayoutParams lp = new LayoutParams(60 * density,LayoutParams.MATCH_PARENT); lp.setMargins(density*3, 0, density*3, 0); for (int keyCode : keys) { if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { Key key = new Key(ctx, keyCode); root.addView(key, lp); } } } private class Key extends AppCompatTextView implements View.OnClickListener, View.OnTouchListener { private static final int TEXT_COLOR = 0xFFFFFFFF; private static final int BUTTON_COLOR = 0x0000FFFF; private static final int INTERESTING_COLOR = 0xFF80DEEA; private static final int BUTTON_PRESSED_COLOR = 0x7FFFFFFF; /** * Keys are displayed in a natural looking way, like "▶" for "RIGHT" */ final Map keyCodesForString = new HashMap() {{ put(KeyEvent.KEYCODE_ESCAPE, "ESC"); put(KeyEvent.KEYCODE_MOVE_HOME, "HOME"); put(KeyEvent.KEYCODE_MOVE_END, "END"); put(KeyEvent.KEYCODE_PAGE_UP, "PGUP"); put(KeyEvent.KEYCODE_PAGE_DOWN, "PGDN"); put(KeyEvent.KEYCODE_CTRL_LEFT, "CTRL"); put(KeyEvent.KEYCODE_ALT_LEFT, "ALT"); put(KeyEvent.KEYCODE_INSERT, "INS"); put(KeyEvent.KEYCODE_FORWARD_DEL, "⌦"); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand put(KeyEvent.KEYCODE_DEL, "⌫"); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand put(KeyEvent.KEYCODE_DPAD_UP, "▲"); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE put(KeyEvent.KEYCODE_DPAD_LEFT, "◀"); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE put(KeyEvent.KEYCODE_DPAD_RIGHT, "▶"); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE put(KeyEvent.KEYCODE_DPAD_DOWN, "▼"); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE put(KeyEvent.KEYCODE_ENTER, "↲"); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS put(KeyEvent.KEYCODE_TAB, "↹"); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR put(KeyEvent.KEYCODE_MINUS, "―"); // U+2015 ― HORIZONTAL BAR put(-1, "⚙"); // U+2699 ⚙ GEAR }}; void sendKey(int keyCode, boolean down) { if (targetListener == null || targetView == null) { Log.e("AdditionalKeyboardView", "target view or target listener is unset"); return; } targetListener.onKey(targetView, keyCode, new KeyEvent((down?KeyEvent.ACTION_DOWN:KeyEvent.ACTION_UP), keyCode)); } boolean toggle; boolean checked = false; int kc; public Key(Context context, int keyCode) { super(context); kc = keyCode; switch (keyCode) { case KeyEvent.KEYCODE_CTRL_LEFT: case KeyEvent.KEYCODE_CTRL_RIGHT: case KeyEvent.KEYCODE_ALT_LEFT: case KeyEvent.KEYCODE_ALT_RIGHT: case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: toggle = true; break; default: toggle = false; } setTextColor(TEXT_COLOR); setBackgroundColor(BUTTON_COLOR); String text = keyCodesForString.get(keyCode); float textWidth = getPaint().measureText(text); setWidth((int) (textWidth + 20 * density)); setText(text); setGravity(Gravity.CENTER); setOnClickListener(this); setOnTouchListener(this); } @Override public void onClick(View v) { if (toggle) { checked = !checked; if (checked) { setTextColor(INTERESTING_COLOR); setBackgroundColor(BUTTON_PRESSED_COLOR); sendKey(kc, true); } else { setTextColor(TEXT_COLOR); setBackgroundColor(BUTTON_COLOR); sendKey(kc, false); } } } private Rect rect; @Override public boolean onTouch(View v, MotionEvent e) { switch(e.getAction()) { case MotionEvent.ACTION_BUTTON_PRESS: case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_DOWN: setTextColor(INTERESTING_COLOR); setBackgroundColor(BUTTON_PRESSED_COLOR); if (!toggle) sendKey(kc, true); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_BUTTON_RELEASE: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: if (!toggle) { setTextColor(TEXT_COLOR); setBackgroundColor(BUTTON_COLOR); sendKey(kc, false); } break; default: break; } return false; } } }