From 2634765bc382c27e2d11dc14174ca80d9cf41e15 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Sat, 4 Mar 2023 15:55:09 +0800 Subject: [PATCH] Improve context menus on old versions of Android * java/org/gnu/emacs/EmacsActivity.java (EmacsActivity): New field `lastClosedMenu'. (onContextMenuClosed): Don't send event if a menu is closed twice in a row. Also, clear wasSubmenuSelected immediately. * java/org/gnu/emacs/EmacsContextMenu.java: Display submenus manually in Android 6.0 and earlier. * java/org/gnu/emacs/EmacsView.java (onCreateContextMenu) (popupMenu): Adjust accordingly. --- java/org/gnu/emacs/EmacsActivity.java | 17 ++++- java/org/gnu/emacs/EmacsContextMenu.java | 81 ++++++++++++++++-------- java/org/gnu/emacs/EmacsView.java | 9 ++- 3 files changed, 76 insertions(+), 31 deletions(-) diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 62bef33fab3..13002d5d0e5 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -65,6 +65,9 @@ public class EmacsActivity extends Activity /* Whether or not this activity is fullscreen. */ private boolean isFullscreen; + /* The last context menu to be closed. */ + private Menu lastClosedMenu; + static { focusedActivities = new ArrayList (); @@ -308,9 +311,19 @@ public class EmacsActivity extends Activity Log.d (TAG, "onContextMenuClosed: " + menu); /* See the comment inside onMenuItemClick. */ + if (EmacsContextMenu.wasSubmenuSelected - && menu.toString ().contains ("ContextMenuBuilder")) - return; + || menu == lastClosedMenu) + { + EmacsContextMenu.wasSubmenuSelected = false; + lastClosedMenu = menu; + return; + } + + /* lastClosedMenu is set because Android apparently calls this + function twice. */ + + lastClosedMenu = null; /* Send a context menu event given that no menu item has already been selected. */ diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index a1bca98daa0..d1a624e68d9 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -58,6 +58,7 @@ public final class EmacsContextMenu public String itemName, tooltip; public EmacsContextMenu subMenu; public boolean isEnabled, isCheckable, isChecked; + public EmacsView inflatedView; @Override public boolean @@ -67,6 +68,34 @@ public final class EmacsContextMenu if (subMenu != null) { + /* Android 6.0 and earlier don't support nested submenus + properly, so display the submenu popup by hand. */ + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) + { + Log.d (TAG, "onMenuItemClick: displaying submenu " + subMenu); + + /* Still set wasSubmenuSelected -- if not set, the + dismissal of this context menu will result in a + context menu event being sent. */ + wasSubmenuSelected = true; + + /* Running a popup menu from inside a click handler + doesn't work, so make sure it is displayed + outside. */ + + inflatedView.post (new Runnable () { + @Override + public void + run () + { + inflatedView.popupMenu (subMenu, 0, 0, true); + } + }); + + return true; + } + /* After opening a submenu within a submenu, Android will send onContextMenuClosed for a ContextMenuBuilder. This will normally confuse Emacs into thinking that the @@ -164,10 +193,11 @@ public final class EmacsContextMenu return item.subMenu; } - /* Add the contents of this menu to MENU. */ + /* Add the contents of this menu to MENU. Assume MENU will be + displayed in INFLATEDVIEW. */ private void - inflateMenuItems (Menu menu) + inflateMenuItems (Menu menu, EmacsView inflatedView) { Intent intent; MenuItem menuItem; @@ -177,26 +207,26 @@ public final class EmacsContextMenu { if (item.subMenu != null) { - try + /* This is a submenu. On versions of Android which + support doing so, create the submenu and add the + contents of the menu to it. + + Note that Android 4.0 and later technically supports + having multiple layers of nested submenus, but if they + are used, onContextMenuClosed becomes unreliable. */ + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - /* This is a submenu. On versions of Android which - support doing so, create the submenu and add the - contents of the menu to it. */ submenu = menu.addSubMenu (item.itemName); - item.subMenu.inflateMenuItems (submenu); - } - catch (UnsupportedOperationException exception) - { - /* This version of Android has a restriction - preventing submenus from being added to submenus. - Inflate everything into the parent menu - instead. */ - item.subMenu.inflateMenuItems (menu); - continue; + item.subMenu.inflateMenuItems (submenu, inflatedView); + + /* This is still needed to set wasSubmenuSelected. */ + menuItem = submenu.getItem (); } + else + menuItem = menu.add (item.itemName); - /* This is still needed to set wasSubmenuSelected. */ - menuItem = submenu.getItem (); + item.inflatedView = inflatedView; menuItem.setOnMenuItemClickListener (item); } else @@ -227,16 +257,14 @@ public final class EmacsContextMenu } } - /* Enter the items in this context menu to MENU. Create each menu - item with an Intent containing a Bundle, where the key - "emacs:menu_item_hi" maps to the high 16 bits of the - corresponding item ID, and the key "emacs:menu_item_low" maps to - the low 16 bits of the item ID. */ + /* Enter the items in this context menu to MENU. + Assume that MENU will be displayed in VIEW; this may lead to + popupMenu being called on VIEW if a submenu is selected. */ public void - expandTo (Menu menu) + expandTo (Menu menu, EmacsView view) { - inflateMenuItems (menu); + inflateMenuItems (menu, view); } /* Return the parent or NULL. */ @@ -260,7 +288,8 @@ public final class EmacsContextMenu /* No submenu has been selected yet. */ wasSubmenuSelected = false; - return window.view.popupMenu (this, xPosition, yPosition); + return window.view.popupMenu (this, xPosition, yPosition, + false); } /* Display this context menu on WINDOW, at xPosition and diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index d2330494bc7..617836d8811 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -464,19 +464,22 @@ public final class EmacsView extends ViewGroup if (contextMenu == null) return; - contextMenu.expandTo (menu); + contextMenu.expandTo (menu, this); } public boolean popupMenu (EmacsContextMenu menu, int xPosition, - int yPosition) + int yPosition, boolean force) { - if (popupActive) + if (popupActive && !force) return false; contextMenu = menu; popupActive = true; + Log.d (TAG, "popupMenu: " + menu + " @" + xPosition + + ", " + yPosition + " " + force); + /* Use showContextMenu (float, float) on N to get actual popup behavior. */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) -- 2.39.2