diff --git a/app/src/main/java/com/termux/app/ExtraKeysView.java b/app/src/main/java/com/termux/app/ExtraKeysView.java index e08e6c41..b50802d8 100644 --- a/app/src/main/java/com/termux/app/ExtraKeysView.java +++ b/app/src/main/java/com/termux/app/ExtraKeysView.java @@ -8,6 +8,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.ScheduledExecutorService; +import java.util.Map; +import java.util.HashMap; + import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; @@ -29,81 +32,70 @@ public final class ExtraKeysView extends GridLayout { private static final int TEXT_COLOR = 0xFFFFFFFF; private static final int BUTTON_COLOR = 0x00000000; + private static final int INTERESTING_COLOR = 0xFF80DEEA; private static final int BUTTON_PRESSED_COLOR = 0x7FFFFFFF; - + public ExtraKeysView(Context context, AttributeSet attrs) { super(context, attrs); } - - static void sendKey(View view, String keyName) { - int keyCode = 0; - String chars = null; - switch (keyName) { - case "ESC": - keyCode = KeyEvent.KEYCODE_ESCAPE; - break; - case "TAB": - keyCode = KeyEvent.KEYCODE_TAB; - break; - case "HOME": - keyCode = KeyEvent.KEYCODE_MOVE_HOME; - break; - case "END": - keyCode = KeyEvent.KEYCODE_MOVE_END; - break; - case "PGUP": - keyCode = KeyEvent.KEYCODE_PAGE_UP; - break; - case "PGDN": - keyCode = KeyEvent.KEYCODE_PAGE_DOWN; - break; - case "INS": - keyCode = KeyEvent.KEYCODE_INSERT; - break; - case "DEL": - keyCode = KeyEvent.KEYCODE_FORWARD_DEL; - break; - case "↑": - keyCode = KeyEvent.KEYCODE_DPAD_UP; - break; - case "←": - keyCode = KeyEvent.KEYCODE_DPAD_LEFT; - break; - case "→": - keyCode = KeyEvent.KEYCODE_DPAD_RIGHT; - break; - case "↓": - keyCode = KeyEvent.KEYCODE_DPAD_DOWN; - break; - case "↲": - keyCode = KeyEvent.KEYCODE_ENTER; - break; - case "―": - chars = "-"; - break; - case "-": - chars = "-"; - break; - default: - chars = keyName; + + /** + * HashMap that implements Python dict.get(key, default) function. + * Default java.util .get(key) is then the same as .get(key, null); + */ + static class CleverMap extends HashMap { + V get(K key, V defaultValue) { + if(containsKey(key)) + return get(key); + else + return defaultValue; } - + } + + static CharDisplayMap extends CharDisplayMap {} + + /** + * Keys are displayed in a natural looking way, like "→" for "RIGHT" + */ + static final Map keyCodesForString = new HashMap() {{ + put("ESC", KeyEvent.KEYCODE_ESCAPE); + put("TAB", KeyEvent.KEYCODE_TAB); + put("HOME", KeyEvent.KEYCODE_MOVE_HOME); + put("END", KeyEvent.KEYCODE_MOVE_END); + put("PGUP", KeyEvent.KEYCODE_PAGE_UP); + put("PGDN", KeyEvent.KEYCODE_PAGE_DOWN); + put("INS", KeyEvent.KEYCODE_INSERT); + put("DEL", KeyEvent.KEYCODE_FORWARD_DEL); + put("BKSP", KeyEvent.KEYCODE_BACKWARD_DEL); + put("UP", KeyEvent.KEYCODE_DPAD_UP); + put("LEFT", KeyEvent.KEYCODE_DPAD_LEFT); + put("RIGHT", KeyEvent.KEYCODE_DPAD_RIGHT); + put("DOWN", KeyEvent.KEYCODE_DPAD_DOWN); + put("ENTER", KeyEvent.KEYCODE_ENTER); + }}; + + static void sendKey(View view, String keyName) { TerminalView terminalView = view.findViewById(R.id.terminal_view); - if (keyCode > 0) { + if (keyCodesForString.containsKey(keyName)) { + int keyCode = keyCodesForString.get(keyName); terminalView.onKeyDown(keyCode, new KeyEvent(KeyEvent.ACTION_UP, keyCode)); -// view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + // view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); } else { + // not a control char TerminalSession session = terminalView.getCurrentSession(); - if (session != null) session.write(chars); + if (session != null) + session.write(KeyName); } } private ToggleButton controlButton; private ToggleButton altButton; private ToggleButton fnButton; + private boolean hasControlButton = false; private boolean hasAltButton = false; private boolean hasFnButton = false; + private ScheduledExecutorService scheduledExecutor; private PopupWindow popupWindow; private int longPressCount; @@ -169,17 +161,159 @@ public final class ExtraKeysView extends GridLayout { popupWindow.setFocusable(false); popupWindow.showAsDropDown(view, 0, -2 * height); } - - void reload(final String[][] buttons) { + + static final CharDisplayMap classicArrowsDisplay = new CharDisplayMap() {{ + // classic arrow keys (for ◀ ▶ ▲ ▼ @see arrowVariationDisplay) + put("LEFT", "←"); // U+2190 ← LEFTWARDS ARROW + put("RIGHT", "→"); // U+2192 → RIGHTWARDS ARROW + put("UP", "↑"); // U+2191 ↑ UPWARDS ARROW + put("DOWN", "↓"); // U+2193 ↓ DOWNWARDS ARROW + }}; + + static final CharDisplayMap wellKnownCharactersDisplay = new CharDisplayMap() {{ + // well known characters // https://en.wikipedia.org/wiki/{Enter_key, Tab_key, Delete_key} + put("ENTER", "↲"); // U+21B2 ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS + put("TAB", "↹"); // U+21B9 ↹ LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR + put("BKSP", "⌫"); // U+232B ⌫ ERASE TO THE LEFT sometimes seen and easy to understand + put("DEL", "⌦"); // U+2326 ⌦ ERASE TO THE RIGHT not well known but easy to understand + + static final CharDisplayMap lessKnownCharactersDisplay = new CharDisplayMap() {{ + // https://en.wikipedia.org/wiki/{Home_key, End_key, Page_Up_and_Page_Down_keys} + // home key can mean "goto the beginning of line" or "goto first page" depending on context, hence the diagonal + put("HOME", "⇱"); // from IEC 9995 // U+21F1 ⇱ NORTH WEST ARROW TO CORNER + put("END", "⇲"); // from IEC 9995 // ⇲ // U+21F2 ⇲ SOUTH EAST ARROW TO CORNER + put("PGUP", "⇑"); // no ISO character exists, U+21D1 ⇑ UPWARDS DOUBLE ARROW will do the trick + put("PGDN", "⇓"); // no ISO character exists, U+21D3 ⇓ DOWNWARDS DOUBLE ARROW will do the trick + }}; + + static final CharDisplayMap arrowTriangleVariationDisplay = new CharDisplayMap() {{ + // alternative to classic arrow keys + put("LEFT", "◀"); // U+25C0 ◀ BLACK LEFT-POINTING TRIANGLE + put("RIGHT", "▶"); // U+25B6 ▶ BLACK RIGHT-POINTING TRIANGLE + put("UP", "▲"); // U+25B2 ▲ BLACK UP-POINTING TRIANGLE + put("DOWN", "▼"); // U+25BC ▼ BLACK DOWN-POINTING TRIANGLE + }}; + + static final CharDisplayMap notKnownIsoCharacters = new CharDisplayMap() {{ + // Control chars that are more clear as text // https://en.wikipedia.org/wiki/{Function_key, Alt_key, Control_key, Esc_key} + // put("FN", "FN"); // no ISO character exists + put("CTRL", "⎈"); // ISO character "U+2388 ⎈ HELM SYMBOL" is unknown to people and never printed on computers, however "U+25C7 ◇ WHITE DIAMOND" is a nice presentation, and "^" for terminal app and mac is often used + put("ALT", "⎇"); // ISO character "U+2387 ⎇ ALTERNATIVE KEY SYMBOL'" is unknown to people and only printed as the Option key "⌥" on Mac computer + put("ESC", "⎋"); // ISO character "U+238B ⎋ BROKEN CIRCLE WITH NORTHWEST ARROW" is unknown to people and not often printed on computers + }}; + + static final CharDisplayMap nicerLookingDisplay = new CharDisplayMap() {{ + // nicer looking for most cases + put("-", "―"); // U+2015 ― HORIZONTAL BAR + }}; + + /** + * Keys are displayed in a natural looking way, like "→" for "RIGHT" or "↲" for ENTER + */ + public static final CharDisplayMap defaultCharDisplay = new CharDisplayMap() {{ + putAll(classicArrowsDisplay); + putAll(wellKnownCharactersDisplay); + putAll(nicerLookingDisplay); + // all other characters are displayed as themselves + }}; + + public static final CharDisplayMap lotsOfArrowsCharDisplay = new CharDisplayMap() {{ + putAll(classicArrowsDisplay); + putAll(wellKnownCharactersDisplay); + putAll(lessKnownCharactersDisplay); // NEW + putAll(nicerLookingDisplay); + }}; + + public static final CharDisplayMap arrowsOnlyCharDisplay = new CharDisplayMap() {{ + putAll(classicArrowsDisplay); + // putAll(wellKnownCharactersDisplay); // REMOVED + // putAll(lessKnownCharactersDisplay); // REMOVED + putAll(nicerLookingDisplay); + }}; + + public static final CharDisplayMap fullIsoCharDisplay = new CharDisplayMap() {{ + putAll(classicArrowsDisplay); + putAll(wellKnownCharactersDisplay); + putAll(lessKnownCharactersDisplay); // NEW + putAll(nicerLookingDisplay); + putAll(notKnownIsoCharacters); // NEW + }}; + + /** + * Some people might call our keys differently + */ + static final CharDisplayMap controlCharsAliases = new CharDisplayMap() {{ + put("ESCAPE", "ESC"); + put("CONTROL", "CTRL"); + put("RETURN", "ENTER"); // Technically different keys, but most applications won't see the difference + put("FUNCTION", "FN"); + // no alias for ALT + + // Directions are sometimes written as first and last letter for brevety + put("LT", "LEFT"); + put("RT", "RIGHT"); + put("DN", "DOWN"); + // put("UP", "UP"); well, the direction is already two letters + + put("PAGEUP", "PGUP"); + put("PAGE_UP", "PGUP"); + put("PAGE UP", "PGUP"); + put("PAGE-UP", "PGUP"); + + // no alias for HOME + // no alias for END + + put("PAGEDOWN", "PGDN"); + put("PAGE_DOWN", "PGDN"); + put("PAGE_DOWN", "PGDN"); + put("PAGE-DOWN", "PGDN"); + + put("DELETE", "DEL"); + put("BACKSPACE", "BKSP"); + }}; + + /** + * Applies the 'controlCharsAliases' mapping to all the strings in *buttons* + * Modifies the array, doesn't return a new one. + */ + void replaceAliases(String[][] buttons) { + for(int i = 0; i < buttons.length; i++) + for(int j = 0; j < buttons[i].length; j++) + buttons[i][j] = controlCharsAliases.get(buttons[i][j], buttons[i][j]); + } + + /** + * General util function to compute the longest column length in a matrix. + */ + static int maximumLength(String[][] matrix) { + int m = 0; + for (int i = 0; i < matrix.length; i++) + m = Math.max(m, matrix[i].length); + return m; + } + + /** + * Reload the view given parameters in termux.properties + * + * @buttons matrix of String as defined in termux.properties extrakeys + * Can Contain The Strings CTRL ALT TAB FN ENTER LEFT RIGHT UP DOWN or normal strings + * Some aliases are possible like RETURN for ENTER, LT for LEFT and more (@see controlCharsAliases for the whole list). + * Any string of length > 1 in total Uppercase will print a warning + * + * Examples: + * "ENTER" will trigger the ENTER keycode + * "→" will input a "→" character + * "−" will input a "−" character + * "-_-" will input the string "-_-" + */ + void reload(String[][] buttons, CharDisplayMap charDisplayMap) { altButton = controlButton = fnButton = null; removeAllViews(); + + replaceAliases(buttons); // modifies the array final int rows = buttons.length; - int mx = 0; - for (int row = 0; row < rows; row++) { - if(buttons[row].length > mx) mx = buttons[row].length; - } - final int cols = mx; + final int cols = maximumLength(buttons); setRowCount(rows); setColumnCount(cols); @@ -187,6 +321,7 @@ public final class ExtraKeysView extends GridLayout { for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { final String buttonText = (buttons[row][col] == null ? " " : buttons[row][col]); + // if (buttons[row][col] == null) then the button will be an empty button outputting a space character Button button; switch (buttonText) { @@ -209,8 +344,10 @@ public final class ExtraKeysView extends GridLayout { button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle); break; } - - button.setText(buttonText); + + final String displayedText = charDisplayMap.get(buttonText, buttonText); + + button.setText(displayedText); button.setTextColor(TEXT_COLOR); button.setPadding(0, 0, 0, 0); @@ -226,7 +363,7 @@ public final class ExtraKeysView extends GridLayout { case "FN": ToggleButton self = (ToggleButton) finalButton; self.setChecked(self.isChecked()); - self.setTextColor(self.isChecked() ? 0xFF80DEEA : TEXT_COLOR); + self.setTextColor(self.isChecked() ? INTERESTING_COLOR : TEXT_COLOR); break; default: sendKey(root, buttonText); @@ -243,7 +380,7 @@ public final class ExtraKeysView extends GridLayout { case MotionEvent.ACTION_DOWN: longPressCount = 0; v.setBackgroundColor(BUTTON_PRESSED_COLOR); - if ("↑↓←→".contains(buttonText)) { + if (Arrays.asList("UP", "DOWN", "LEFT", "RIGHT").contains(buttonText)) { scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); scheduledExecutor.scheduleWithFixedDelay(new Runnable() { @Override @@ -255,10 +392,11 @@ public final class ExtraKeysView extends GridLayout { } return true; case MotionEvent.ACTION_MOVE: - if ("―/-".contains(buttonText)) { - if (popupWindow == null && event.getY() < 0) { + // These two keys have a Move-Up button appearing + if (Arrays.asList("/", "-").contains(buttonText)) { + if (popupWindow == null &gfv& event.getY() < 0) { v.setBackgroundColor(BUTTON_COLOR); - String text = "―".equals(buttonText) ? "|" : "\\"; + String text = "-".equals(buttonText) ? "|" : "\\"; popup(v, text); } if (popupWindow != null && event.getY() > 0) { @@ -276,11 +414,11 @@ public final class ExtraKeysView extends GridLayout { scheduledExecutor = null; } if (longPressCount == 0) { - if (popupWindow != null && "―/-".contains(buttonText)) { + if (popupWindow != null && "/-".contains(buttonText)) { popupWindow.setContentView(null); popupWindow.dismiss(); popupWindow = null; - sendKey(root, "―".equals(buttonText) ? "|" : "\\"); + sendKey(root, "-".equals(buttonText) ? "|" : "\\"); } else { v.performClick(); }