From: Po Lu Date: Sun, 15 Jan 2023 07:45:29 +0000 (+0800) Subject: Implement submenus on Android X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=da9ae10636b84b88e9eb9c827b03cdaabd1611d1;p=emacs.git 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. --- diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 4b96a376987..79c0991a5d3 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -27,6 +27,7 @@ import android.app.Activity; 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) diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 02dd1c7efa9..00e204c9949 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -30,6 +30,7 @@ import android.os.Bundle; 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 @@ public class EmacsContextMenu { 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 @@ public class EmacsContextMenu { Intent intent; MenuItem menuItem; - Menu submenu; + SubMenu submenu; for (Item item : menuItems) { @@ -153,7 +171,11 @@ public class EmacsContextMenu /* 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 @@ public class EmacsContextMenu public EmacsContextMenu parent () { - return parent; + return this.parent; } /* Like display, but does the actual work and runs in the main @@ -197,6 +219,9 @@ public class EmacsContextMenu 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); } diff --git a/java/org/gnu/emacs/EmacsDrawRectangle.java b/java/org/gnu/emacs/EmacsDrawRectangle.java index 84ff498847b..b42e9556e8c 100644 --- a/java/org/gnu/emacs/EmacsDrawRectangle.java +++ b/java/org/gnu/emacs/EmacsDrawRectangle.java @@ -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); diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index 4a80f88edcf..2f3a732ea7c 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -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); diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index f935b63fa0d..ca38f93dc98 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -108,7 +108,7 @@ public class EmacsService extends Service { AssetManager manager; Context app_context; - String filesDir, libDir; + String filesDir, libDir, cacheDir; double pixelDensityX; double pixelDensityY; @@ -126,12 +126,13 @@ public class EmacsService extends Service 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 @@ public class EmacsService extends Service { 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 (); + } + } + } }; diff --git a/src/android.c b/src/android.c index ed162a903ba..3a965286460 100644 --- a/src/android.c +++ b/src/android.c @@ -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 diff --git a/src/androidfns.c b/src/androidfns.c index ab136bc2722..bb37c415069 100644 --- a/src/androidfns.c +++ b/src/androidfns.c @@ -26,6 +26,7 @@ along with GNU Emacs. If not, see . */ #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); diff --git a/src/androidgui.h b/src/androidgui.h index 0e075fda95e..9df5b073a7c 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -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 diff --git a/src/androidmenu.c b/src/androidmenu.c index 7522f9c5a52..6fb4963174b 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -28,6 +28,8 @@ along with GNU Emacs. If not, see . */ #ifndef ANDROID_STUBIFY +#include + /* 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); diff --git a/src/androidterm.c b/src/androidterm.c index 4017fec60a5..6f452a52d85 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -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) diff --git a/src/menu.c b/src/menu.c index 73d4215b94b..e1f899858d3 100644 --- a/src/menu.c +++ b/src/menu.c @@ -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)))