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.
This commit is contained in:
Po Lu 2023-03-04 15:55:09 +08:00
parent 39a7e6b79f
commit 2634765bc3
3 changed files with 79 additions and 34 deletions

View file

@ -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<EmacsActivity> ();
@ -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. */

View file

@ -58,6 +58,7 @@ private static class Item implements MenuItem.OnMenuItemClickListener
public String itemName, tooltip;
public EmacsContextMenu subMenu;
public boolean isEnabled, isCheckable, isChecked;
public EmacsView inflatedView;
@Override
public boolean
@ -67,6 +68,34 @@ private static class Item implements MenuItem.OnMenuItemClickListener
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 @@ private static class Item implements MenuItem.OnMenuItemClickListener
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 @@ private static class Item implements MenuItem.OnMenuItemClickListener
{
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. */
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;
}
/* This is a submenu. On versions of Android which
support doing so, create the submenu and add the
contents of the menu to it.
/* This is still needed to set wasSubmenuSelected. */
menuItem = submenu.getItem ();
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)
{
submenu = menu.addSubMenu (item.itemName);
item.subMenu.inflateMenuItems (submenu, inflatedView);
/* This is still needed to set wasSubmenuSelected. */
menuItem = submenu.getItem ();
}
else
menuItem = menu.add (item.itemName);
item.inflatedView = inflatedView;
menuItem.setOnMenuItemClickListener (item);
}
else
@ -227,16 +257,14 @@ private static class Item implements MenuItem.OnMenuItemClickListener
}
}
/* 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 @@ private static class Item implements MenuItem.OnMenuItemClickListener
/* 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

View file

@ -464,19 +464,22 @@ else if (child.getVisibility () != GONE)
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)