[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
feature/android da9ae10636 1/3: Implement submenus on Android
From: |
Po Lu |
Subject: |
feature/android da9ae10636 1/3: Implement submenus on Android |
Date: |
Mon, 16 Jan 2023 06:50:57 -0500 (EST) |
branch: feature/android
commit da9ae10636b84b88e9eb9c827b03cdaabd1611d1
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>
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.
---
java/org/gnu/emacs/EmacsActivity.java | 12 ++-
java/org/gnu/emacs/EmacsContextMenu.java | 31 ++++++-
java/org/gnu/emacs/EmacsDrawRectangle.java | 2 +-
java/org/gnu/emacs/EmacsNative.java | 4 +
java/org/gnu/emacs/EmacsService.java | 36 +++++++-
src/android.c | 38 ++++++++-
src/androidfns.c | 8 ++
src/androidgui.h | 1 +
src/androidmenu.c | 128 ++++++++++++++++++++++++++---
src/androidterm.c | 10 +++
src/menu.c | 9 +-
11 files changed, 255 insertions(+), 24 deletions(-)
diff --git a/java/org/gnu/emacs/EmacsActivity.java
b/java/org/gnu/emacs/EmacsActivity.java
index 4b96a37698..79c0991a5d 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 02dd1c7efa..00e204c994 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 84ff498847..b42e9556e8 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 4a80f88edc..2f3a732ea7 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 f935b63fa0..ca38f93dc9 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 ed162a903b..3a96528646 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 ab136bc272..bb37c41506 100644
--- a/src/androidfns.c
+++ b/src/androidfns.c
@@ -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);
diff --git a/src/androidgui.h b/src/androidgui.h
index 0e075fda95..9df5b073a7 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 7522f9c5a5..6fb4963174 100644
--- a/src/androidmenu.c
+++ b/src/androidmenu.c
@@ -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);
diff --git a/src/androidterm.c b/src/androidterm.c
index 4017fec60a..6f452a52d8 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 73d4215b94..e1f899858d 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)))