1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-12-01 08:17:38 +00:00

Implement submenus on Android

* java/org/gnu/emacs/EmacsActivity.java (onCreate): Set the
default theme to Theme.DeviceDefault.NoActionBar if possible.
(onContextMenuClosed): Add hack for Android bug.
* java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu)
(onMenuItemClick): Set flag upon submenu selection.
(inflateMenuItems): Set onClickListener for submenus as well.
(display1): Clear new flag.
* java/org/gnu/emacs/EmacsDrawRectangle.java (perform): Fix
rectangle bounds.
* java/org/gnu/emacs/EmacsNative.java (EmacsNative):
* java/org/gnu/emacs/EmacsService.java (onCreate): Pass cache
directory.
(sync): New function.
* src/android.c (struct android_emacs_service): New method
`sync'.
(setEmacsParams, initEmacs): Handle cache directory.
(android_init_emacs_service): Initialize new method `sync'.
(android_sync): New function.
* src/androidfns.c (Fx_show_tip): Call both functions.
* src/androidgui.h: Update prototypes.
* src/androidmenu.c (struct android_menu_subprefix)
(android_free_subprefixes, android_menu_show): Handle submenu
prefixes correctly.
* src/androidterm.c (handle_one_android_event): Clear help echo
on MotionNotify like on X.
* src/menu.c (single_menu_item): Enable submenus on Android.
This commit is contained in:
Po Lu 2023-01-15 15:45:29 +08:00
parent a336fd98a1
commit da9ae10636
11 changed files with 255 additions and 24 deletions

View File

@ -27,6 +27,7 @@
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Build;
import android.util.Log;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
@ -162,7 +163,11 @@ public class EmacsActivity extends Activity
FrameLayout.LayoutParams params;
/* Set the theme to one without a title bar. */
setTheme (android.R.style.Theme_NoTitleBar);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
setTheme (android.R.style.Theme_DeviceDefault_NoActionBar);
else
setTheme (android.R.style.Theme_NoTitleBar);
params = new FrameLayout.LayoutParams (LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
@ -235,6 +240,11 @@ public class EmacsActivity extends Activity
{
Log.d (TAG, "onContextMenuClosed: " + menu);
/* See the comment inside onMenuItemClick. */
if (EmacsContextMenu.wasSubmenuSelected
&& menu.toString ().contains ("ContextMenuBuilder"))
return;
/* Send a context menu event given that no menu item has already
been selected. */
if (!EmacsContextMenu.itemAlreadySelected)

View File

@ -30,6 +30,7 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.SubMenu;
import android.util.Log;
@ -47,6 +48,9 @@ public class EmacsContextMenu
/* Whether or not an item was selected. */
public static boolean itemAlreadySelected;
/* Whether or not a submenu was selected. */
public static boolean wasSubmenuSelected;
private class Item implements MenuItem.OnMenuItemClickListener
{
public int itemID;
@ -60,6 +64,20 @@ private class Item implements MenuItem.OnMenuItemClickListener
{
Log.d (TAG, "onMenuItemClick: " + itemName + " (" + itemID + ")");
if (subMenu != null)
{
/* After opening a submenu within a submenu, Android will
send onContextMenuClosed for a ContextMenuBuilder. This
will normally confuse Emacs into thinking that the
context menu has been dismissed. Wrong!
Setting this flag makes EmacsActivity to only handle
SubMenuBuilder being closed, which always means the menu
has actually been dismissed. */
wasSubmenuSelected = true;
return false;
}
/* Send a context menu event. */
EmacsNative.sendContextMenu ((short) 0, itemID);
@ -144,7 +162,7 @@ private class Item implements MenuItem.OnMenuItemClickListener
{
Intent intent;
MenuItem menuItem;
Menu submenu;
SubMenu submenu;
for (Item item : menuItems)
{
@ -153,7 +171,11 @@ private class Item implements MenuItem.OnMenuItemClickListener
/* This is a submenu. Create the submenu and add the
contents of the menu to it. */
submenu = menu.addSubMenu (item.itemName);
inflateMenuItems (submenu);
item.subMenu.inflateMenuItems (submenu);
/* This is still needed to set wasSubmenuSelected. */
menuItem = submenu.getItem ();
menuItem.setOnMenuItemClickListener (item);
}
else
{
@ -184,7 +206,7 @@ private class Item implements MenuItem.OnMenuItemClickListener
public EmacsContextMenu
parent ()
{
return parent;
return this.parent;
}
/* Like display, but does the actual work and runs in the main
@ -197,6 +219,9 @@ private class Item implements MenuItem.OnMenuItemClickListener
send 0 in response to the context menu being closed. */
itemAlreadySelected = false;
/* No submenu has been selected yet. */
wasSubmenuSelected = false;
return window.view.popupMenu (this, xPosition, yPosition);
}

View File

@ -59,7 +59,7 @@ public class EmacsDrawRectangle
}
paint = gc.gcPaint;
rect = new Rect (x + 1, y + 1, x + width, y + height);
rect = new Rect (x, y, x + width, y + height);
paint.setStyle (Paint.Style.STROKE);

View File

@ -38,6 +38,9 @@ public class EmacsNative
libDir must be the package's data storage location for native
libraries. It is used as PATH.
cacheDir must be the package's cache directory. It is used as
the `temporary-file-directory'.
pixelDensityX and pixelDensityY are the DPI values that will be
used by Emacs.
@ -45,6 +48,7 @@ public class EmacsNative
public static native void setEmacsParams (AssetManager assetManager,
String filesDir,
String libDir,
String cacheDir,
float pixelDensityX,
float pixelDensityY,
EmacsService emacsService);

View File

@ -108,7 +108,7 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
{
AssetManager manager;
Context app_context;
String filesDir, libDir;
String filesDir, libDir, cacheDir;
double pixelDensityX;
double pixelDensityY;
@ -126,12 +126,13 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
parameters. */
filesDir = app_context.getFilesDir ().getCanonicalPath ();
libDir = getLibraryDirectory ();
cacheDir = app_context.getCacheDir ().getCanonicalPath ();
Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
+ " and libDir = " + libDir);
EmacsNative.setEmacsParams (manager, filesDir, libDir,
(float) pixelDensityX,
cacheDir, (float) pixelDensityX,
(float) pixelDensityY,
this);
@ -407,4 +408,35 @@ else if (apiLevel >= Build.VERSION_CODES.DONUT)
{
return KeyEvent.keyCodeToString (keysym);
}
public void
sync ()
{
Runnable runnable;
runnable = new Runnable () {
public void
run ()
{
synchronized (this)
{
notify ();
}
}
};
synchronized (runnable)
{
runOnUiThread (runnable);
try
{
runnable.wait ();
}
catch (InterruptedException e)
{
EmacsNative.emacsAbort ();
}
}
}
};

View File

@ -88,6 +88,7 @@ struct android_emacs_service
jmethodID get_screen_height;
jmethodID detect_mouse;
jmethodID name_keysym;
jmethodID sync;
};
struct android_emacs_pixmap
@ -116,15 +117,18 @@ static AAssetManager *asset_manager;
/* Whether or not Emacs has been initialized. */
static int emacs_initialized;
/* The path used to store site-lisp. */
/* The directory used to store site-lisp. */
char *android_site_load_path;
/* The path used to store native libraries. */
/* The directory used to store native libraries. */
char *android_lib_dir;
/* The path used to store game files. */
/* The directory used to store game files. */
char *android_game_path;
/* The directory used to store temporary files. */
char *android_cache_dir;
/* The display's pixel densities. */
double android_pixel_density_x, android_pixel_density_y;
@ -911,6 +915,7 @@ JNIEXPORT void JNICALL
NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
jobject local_asset_manager,
jobject files_dir, jobject libs_dir,
jobject cache_dir,
jfloat pixel_density_x,
jfloat pixel_density_y,
jobject emacs_service_object)
@ -986,6 +991,20 @@ NATIVE_NAME (setEmacsParams) (JNIEnv *env, jobject object,
(*env)->ReleaseStringUTFChars (env, (jstring) libs_dir,
java_string);
java_string = (*env)->GetStringUTFChars (env, (jstring) cache_dir,
NULL);
if (!java_string)
emacs_abort ();
android_cache_dir = strdup ((const char *) java_string);
if (!android_files_dir)
emacs_abort ();
(*env)->ReleaseStringUTFChars (env, (jstring) cache_dir,
java_string);
/* Calculate the site-lisp path. */
android_site_load_path = malloc (PATH_MAX + 1);
@ -1083,6 +1102,7 @@ android_init_emacs_service (void)
FIND_METHOD (get_screen_height, "getScreenHeight", "(Z)I");
FIND_METHOD (detect_mouse, "detectMouse", "()Z");
FIND_METHOD (name_keysym, "nameKeysym", "(I)Ljava/lang/String;");
FIND_METHOD (sync, "sync", "()V");
#undef FIND_METHOD
}
@ -1216,6 +1236,9 @@ NATIVE_NAME (initEmacs) (JNIEnv *env, jobject object, jarray argv)
/* Set HOME to the app data directory. */
setenv ("HOME", android_files_dir, 1);
/* Set TMPDIR to the temporary files directory. */
setenv ("TMPDIR", android_cache_dir, 1);
/* Set the cwd to that directory as well. */
if (chdir (android_files_dir))
__android_log_print (ANDROID_LOG_WARN, __func__,
@ -3519,6 +3542,15 @@ android_get_keysym_name (int keysym, char *name_return, size_t size)
ANDROID_DELETE_LOCAL_REF (string);
}
void
android_sync (void)
{
(*android_java_env)->CallVoidMethod (android_java_env,
emacs_service,
service_class.sync);
android_exception_check ();
}
#undef faccessat

View File

@ -26,6 +26,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "blockinput.h"
#include "keyboard.h"
#include "buffer.h"
#include "androidgui.h"
#ifndef ANDROID_STUBIFY
@ -2282,6 +2283,13 @@ DEFUN ("x-show-tip", Fx_show_tip, Sx_show_tip, 1, 6, 0,
android_map_raised (FRAME_ANDROID_WINDOW (tip_f));
unblock_input ();
/* Synchronize with the UI thread. This is required to prevent ugly
black splotches. */
android_sync ();
/* Garbage the tip frame too. */
SET_FRAME_GARBAGED (tip_f);
w->must_be_updated_p = true;
update_single_window (w);
flush_frame (tip_f);

View File

@ -504,6 +504,7 @@ extern void android_move_resize_window (android_window, int, int,
extern void android_map_raised (android_window);
extern void android_translate_coordinates (android_window, int,
int, int *, int *);
extern void android_sync (void);
#endif

View File

@ -28,6 +28,8 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#ifndef ANDROID_STUBIFY
#include <android/log.h>
/* Flag indicating whether or not a popup menu has been posted and not
yet popped down. */
@ -188,6 +190,35 @@ android_process_events_for_menu (int *id)
*id = x_display_list->menu_event_id;
}
/* Structure describing a ``subprefix'' in the menu. */
struct android_menu_subprefix
{
/* The subprefix above. */
struct android_menu_subprefix *last;
/* The subprefix itself. */
Lisp_Object subprefix;
};
/* Free the subprefixes starting from *DATA. */
static void
android_free_subprefixes (void *data)
{
struct android_menu_subprefix **head, *subprefix;
head = data;
while (*head)
{
subprefix = *head;
*head = subprefix->last;
xfree (subprefix);
}
}
Lisp_Object
android_menu_show (struct frame *f, int x, int y, int menuflags,
Lisp_Object title, const char **error_name)
@ -198,13 +229,15 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
Lisp_Object pane_name, prefix;
const char *pane_string;
specpdl_ref count, count1;
Lisp_Object item_name, enable, def, tem;
Lisp_Object item_name, enable, def, tem, entry;
jmethodID method;
jobject store;
bool rc;
jobject window;
int id, item_id;
int id, item_id, submenu_depth;
struct android_dismiss_menu_data data;
struct android_menu_subprefix *subprefix, *temp_subprefix;
struct android_menu_subprefix *subprefix_1;
count = SPECPDL_INDEX ();
@ -232,7 +265,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
android_push_local_frame ();
/* Iterate over the menu. */
i = 0;
i = 0, submenu_depth = 0;
while (i < menu_items_used)
{
@ -241,6 +274,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
/* This is the start of a new submenu. However, it can be
ignored here. */
i += 1;
submenu_depth += 1;
}
else if (EQ (AREF (menu_items, i), Qlambda))
{
@ -256,9 +290,18 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
if (store != context_menu)
ANDROID_DELETE_LOCAL_REF (store);
i += 1;
submenu_depth -= 1;
eassert (current_context_menu);
if (!current_context_menu || submenu_depth < 0)
{
__android_log_print (ANDROID_LOG_FATAL, __func__,
"unbalanced submenu pop in menu_items");
emacs_abort ();
}
}
else if (EQ (AREF (menu_items, i), Qt)
&& submenu_depth != 0)
i += MENU_ITEMS_PANE_LENGTH;
else if (EQ (AREF (menu_items, i), Qquote))
i += 1;
else if (EQ (AREF (menu_items, i), Qt))
@ -300,8 +343,8 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
/* This is an actual menu item (or submenu). Add it to the
menu. */
if (i + MENU_ITEMS_ITEM_LENGTH < menu_items_used &&
NILP (AREF (menu_items, i + MENU_ITEMS_ITEM_LENGTH)))
if (i + MENU_ITEMS_ITEM_LENGTH < menu_items_used
&& NILP (AREF (menu_items, i + MENU_ITEMS_ITEM_LENGTH)))
{
/* This is a submenu. Add it. */
title_string = (!NILP (item_name)
@ -312,7 +355,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
= (*android_java_env)->CallObjectMethod (android_java_env,
current_context_menu,
menu_class.add_submenu,
title_string);
title_string, NULL);
android_exception_check ();
if (store != context_menu)
@ -385,13 +428,78 @@ android_menu_show (struct frame *f, int x, int y, int menuflags,
/* This means no menu item was selected. */
goto finish;
/* id is an index into menu_items. Check that it remains
valid. */
/* This means the id is invalid. */
if (id >= ASIZE (menu_items))
goto finish;
/* Now return the menu item at that location. */
tem = AREF (menu_items, id);
tem = Qnil;
subprefix = NULL;
record_unwind_protect_ptr (android_free_subprefixes, &subprefix);
/* Find the selected item, and its pane, to return
the proper value. */
prefix = entry = Qnil;
i = 0;
while (i < menu_items_used)
{
if (NILP (AREF (menu_items, i)))
{
temp_subprefix = xmalloc (sizeof *temp_subprefix);
temp_subprefix->last = subprefix;
subprefix = temp_subprefix;
subprefix->subprefix = prefix;
prefix = entry;
i++;
}
else if (EQ (AREF (menu_items, i), Qlambda))
{
prefix = subprefix->subprefix;
temp_subprefix = subprefix->last;
xfree (subprefix);
subprefix = temp_subprefix;
i++;
}
else if (EQ (AREF (menu_items, i), Qt))
{
prefix
= AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
i += MENU_ITEMS_PANE_LENGTH;
}
/* Ignore a nil in the item list.
It's meaningful only for dialog boxes. */
else if (EQ (AREF (menu_items, i), Qquote))
i += 1;
else
{
entry = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
if (i + MENU_ITEMS_ITEM_VALUE == id)
{
if (menuflags & MENU_KEYMAPS)
{
entry = list1 (entry);
if (!NILP (prefix))
entry = Fcons (prefix, entry);
for (subprefix_1 = subprefix; subprefix_1;
subprefix_1 = subprefix_1->last)
if (!NILP (subprefix_1->subprefix))
entry = Fcons (subprefix_1->subprefix, entry);
}
tem = entry;
}
i += MENU_ITEMS_ITEM_LENGTH;
}
}
Fprint (tem, Qexternal_debugging_output);
unblock_input ();
return unbind_to (count, tem);

View File

@ -689,6 +689,16 @@ handle_one_android_event (struct android_display_info *dpyinfo,
goto OTHER;
case ANDROID_MOTION_NOTIFY:
previous_help_echo_string = help_echo_string;
help_echo_string = Qnil;
if (hlinfo->mouse_face_hidden)
{
hlinfo->mouse_face_hidden = false;
clear_mouse_face (hlinfo);
}
f = any;
if (f)

View File

@ -167,7 +167,7 @@ ensure_menu_items (int items)
}
}
#ifdef HAVE_EXT_MENU_BAR
#if defined HAVE_EXT_MENU_BAR || defined HAVE_ANDROID
/* Begin a submenu. */
@ -191,7 +191,7 @@ push_submenu_end (void)
menu_items_submenu_depth--;
}
#endif /* HAVE_EXT_MENU_BAR */
#endif /* HAVE_EXT_MENU_BAR || HAVE_ANDROID */
/* Indicate boundary between left and right. */
@ -420,8 +420,9 @@ single_menu_item (Lisp_Object key, Lisp_Object item, Lisp_Object dummy, void *sk
AREF (item_properties, ITEM_PROPERTY_SELECTED),
AREF (item_properties, ITEM_PROPERTY_HELP));
#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS) \
|| defined (HAVE_NTGUI) || defined (HAVE_HAIKU) || defined (HAVE_PGTK)
#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS) \
|| defined (HAVE_NTGUI) || defined (HAVE_HAIKU) || defined (HAVE_PGTK) \
|| defined (HAVE_ANDROID)
/* Display a submenu using the toolkit. */
if (FRAME_WINDOW_P (XFRAME (Vmenu_updating_frame))
&& ! (NILP (map) || NILP (enabled)))