From: Po Lu Date: Sun, 15 Jan 2023 03:57:10 +0000 (+0800) Subject: Implement toolkit menus on Android X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=6e2bc91d924fbeb0ad5728e0424eabc905c0d366;p=emacs.git Implement toolkit menus on Android * java/org/gnu/emacs/EmacsActivity.java (onContextMenuClosed): New function. * java/org/gnu/emacs/EmacsContextMenu.java (EmacsContextMenu): New field `itemAlreadySelected'. (onMenuItemClick): New function. (inflateMenuItems): Attach onClickListener as appropriate. (display1): Clear itemAlreadySelected. (display): Fix runnable synchronization. * java/org/gnu/emacs/EmacsNative.java (sendContextMenu): New function. * java/org/gnu/emacs/EmacsView.java (popupMenu): (cancelPopupMenu): Set popupactive correctly. * src/android.c (android_run_select_thread): Fix android_select again. (android_wait_event): New function. * src/android.h: Update prototypes. * src/androidgui.h (enum android_event_type): New `ANDROID_CONTEXT_MENU' event. (struct android_menu_event, union android_event): Add new event. * src/androidmenu.c (struct android_emacs_context_menu): New structure. (android_init_emacs_context_menu): Add `dismiss' method. (struct android_dismiss_menu_data): New structure. (android_dismiss_menu, android_process_events_for_menu): New functions. (android_menu_show): Set an actual item ID. (popup_activated): Define when stubify as well. (Fmenu_or_popup_active_p): New function. (syms_of_androidmenu): New function. * src/androidterm.c (handle_one_android_event): Handle context menu events. * src/androidterm.h (struct android_display_info): New field for menu item ID. * src/emacs.c (android_emacs_init): Call syms_of_androidmenu. * src/xdisp.c (note_mouse_highlight): Return if popup_activated on Android as well. --- diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java index 4cd286d1e89..4b96a376987 100644 --- a/java/org/gnu/emacs/EmacsActivity.java +++ b/java/org/gnu/emacs/EmacsActivity.java @@ -30,6 +30,7 @@ import android.os.Bundle; import android.util.Log; import android.widget.FrameLayout; import android.widget.FrameLayout.LayoutParams; +import android.view.Menu; public class EmacsActivity extends Activity implements EmacsWindowAttachmentManager.WindowConsumer @@ -227,4 +228,18 @@ public class EmacsActivity extends Activity EmacsWindowAttachmentManager.MANAGER.noticeDeiconified (this); super.onResume (); } + + @Override + public void + onContextMenuClosed (Menu menu) + { + Log.d (TAG, "onContextMenuClosed: " + menu); + + /* Send a context menu event given that no menu item has already + been selected. */ + if (!EmacsContextMenu.itemAlreadySelected) + EmacsNative.sendContextMenu ((short) 0, 0); + + super.onContextMenuClosed (menu); + } }; diff --git a/java/org/gnu/emacs/EmacsContextMenu.java b/java/org/gnu/emacs/EmacsContextMenu.java index 8d7ae08b257..02dd1c7efa9 100644 --- a/java/org/gnu/emacs/EmacsContextMenu.java +++ b/java/org/gnu/emacs/EmacsContextMenu.java @@ -31,6 +31,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.util.Log; + import android.widget.PopupMenu; /* Context menu implementation. This object is built from JNI and @@ -40,12 +42,31 @@ import android.widget.PopupMenu; public class EmacsContextMenu { - private class Item + private static final String TAG = "EmacsContextMenu"; + + /* Whether or not an item was selected. */ + public static boolean itemAlreadySelected; + + private class Item implements MenuItem.OnMenuItemClickListener { public int itemID; public String itemName; public EmacsContextMenu subMenu; public boolean isEnabled; + + @Override + public boolean + onMenuItemClick (MenuItem item) + { + Log.d (TAG, "onMenuItemClick: " + itemName + " (" + itemID + ")"); + + /* Send a context menu event. */ + EmacsNative.sendContextMenu ((short) 0, itemID); + + /* Say that an item has already been selected. */ + itemAlreadySelected = true; + return true; + } }; public List menuItems; @@ -137,6 +158,7 @@ public class EmacsContextMenu else { menuItem = menu.add (item.itemName); + menuItem.setOnMenuItemClickListener (item); /* If the item ID is zero, then disable the item. */ if (item.itemID == 0 || !item.isEnabled) @@ -171,6 +193,10 @@ public class EmacsContextMenu private boolean display1 (EmacsWindow window, int xPosition, int yPosition) { + /* Set this flag to false. It is used to decide whether or not to + send 0 in response to the context menu being closed. */ + itemAlreadySelected = false; + return window.view.popupMenu (this, xPosition, yPosition); } @@ -199,15 +225,39 @@ public class EmacsContextMenu } }; - try + synchronized (runnable) { - runnable.wait (); - } - catch (InterruptedException e) - { - EmacsNative.emacsAbort (); + EmacsService.SERVICE.runOnUiThread (runnable); + + try + { + runnable.wait (); + } + catch (InterruptedException e) + { + EmacsNative.emacsAbort (); + } } return rc.thing; } + + /* Dismiss this context menu. WINDOW is the window where the + context menu is being displayed. */ + + public void + dismiss (final EmacsWindow window) + { + Runnable runnable; + + EmacsService.SERVICE.runOnUiThread (new Runnable () { + @Override + public void + run () + { + window.view.cancelPopupMenu (); + itemAlreadySelected = false; + } + }); + } }; diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index a11e509cd7f..4a80f88edcf 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -124,6 +124,9 @@ public class EmacsNative /* Send an ANDROID_DEICONIFIED event. */ public static native void sendDeiconified (short window); + /* Send an ANDROID_CONTEXT_MENU event. */ + public static native void sendContextMenu (short window, int menuEventID); + static { System.loadLibrary ("emacs"); diff --git a/java/org/gnu/emacs/EmacsView.java b/java/org/gnu/emacs/EmacsView.java index 1391f630be0..445d8ffa023 100644 --- a/java/org/gnu/emacs/EmacsView.java +++ b/java/org/gnu/emacs/EmacsView.java @@ -453,6 +453,7 @@ public class EmacsView extends ViewGroup return false; contextMenu = menu; + popupActive = true; /* On API 21 or later, use showContextMenu (float, float). */ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) @@ -469,5 +470,6 @@ public class EmacsView extends ViewGroup + " popupActive set"); contextMenu = null; + popupActive = false; } }; diff --git a/src/android.c b/src/android.c index 5e5e28c60ca..ed162a903ba 100644 --- a/src/android.c +++ b/src/android.c @@ -236,7 +236,7 @@ static sem_t android_pselect_sem, android_pselect_start_sem; static void * android_run_select_thread (void *data) { - sigset_t signals; + sigset_t signals, sigset; int rc; sigfillset (&signals); @@ -259,7 +259,11 @@ android_run_select_thread (void *data) /* Make sure SIGUSR1 can always wake pselect up. */ if (android_pselect_sigset) - sigdelset (android_pselect_sigset, SIGUSR1); + { + sigset = *android_pselect_sigset; + sigdelset (&sigset, SIGUSR1); + android_pselect_sigset = &sigset; + } else android_pselect_sigset = &signals; @@ -356,6 +360,23 @@ android_pending (void) return i; } +/* Wait for events to become available synchronously. Return once an + event arrives. */ + +void +android_wait_event (void) +{ + pthread_mutex_lock (&event_queue.mutex); + + /* Wait for events to appear if there are none available to + read. */ + if (!event_queue.num_events) + pthread_cond_wait (&event_queue.read_var, + &event_queue.mutex); + + pthread_mutex_unlock (&event_queue.mutex); +} + void android_next_event (union android_event *event_return) { @@ -1472,6 +1493,8 @@ NATIVE_NAME (sendIconified) (JNIEnv *env, jobject object, event.iconified.type = ANDROID_ICONIFIED; event.iconified.window = window; + + android_write_event (&event); } extern JNIEXPORT void JNICALL @@ -1482,6 +1505,21 @@ NATIVE_NAME (sendDeiconified) (JNIEnv *env, jobject object, event.iconified.type = ANDROID_DEICONIFIED; event.iconified.window = window; + + android_write_event (&event); +} + +extern JNIEXPORT void JNICALL +NATIVE_NAME (sendContextMenu) (JNIEnv *env, jobject object, + jshort window, jint menu_event_id) +{ + union android_event event; + + event.menu.type = ANDROID_CONTEXT_MENU; + event.menu.window = window; + event.menu.menu_event_id = menu_event_id; + + android_write_event (&event); } #pragma clang diagnostic pop diff --git a/src/android.h b/src/android.h index 98f2494e9a3..e68e0a51fbf 100644 --- a/src/android.h +++ b/src/android.h @@ -89,6 +89,7 @@ extern jstring android_build_string (Lisp_Object); extern void android_exception_check (void); extern void android_get_keysym_name (int, char *, size_t); +extern void android_wait_event (void); diff --git a/src/androidgui.h b/src/androidgui.h index 8450a1f637b..0e075fda95e 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -233,6 +233,7 @@ enum android_event_type ANDROID_WHEEL, ANDROID_ICONIFIED, ANDROID_DEICONIFIED, + ANDROID_CONTEXT_MENU, }; struct android_any_event @@ -371,6 +372,18 @@ struct android_iconify_event android_window window; }; +struct android_menu_event +{ + /* Type of the event. */ + enum android_event_type type; + + /* Window associated with the event. Always None. */ + android_window window; + + /* Menu event ID. */ + int menu_event_id; +}; + union android_event { enum android_event_type type; @@ -395,6 +408,9 @@ union android_event /* This has no parallel in X because Android doesn't have window properties. */ struct android_iconify_event iconified; + + /* This is only used to transmit selected menu items. */ + struct android_menu_event menu; }; enum diff --git a/src/androidmenu.c b/src/androidmenu.c index 0f0c6f4ef1f..7522f9c5a52 100644 --- a/src/androidmenu.c +++ b/src/androidmenu.c @@ -54,6 +54,7 @@ struct android_emacs_context_menu jmethodID add_pane; jmethodID parent; jmethodID display; + jmethodID dismiss; }; /* Identifiers associated with the EmacsContextMenu class. */ @@ -101,6 +102,7 @@ android_init_emacs_context_menu (void) FIND_METHOD (add_pane, "addPane", "(Ljava/lang/String;)V"); FIND_METHOD (parent, "parent", "()Lorg/gnu/emacs/EmacsContextMenu;"); FIND_METHOD (display, "display", "(Lorg/gnu/emacs/EmacsWindow;II)Z"); + FIND_METHOD (dismiss, "dismiss", "(Lorg/gnu/emacs/EmacsWindow;)V"); #undef FIND_METHOD #undef FIND_METHOD_STATIC @@ -130,6 +132,62 @@ android_push_local_frame (void) record_unwind_protect_void (android_unwind_local_frame); } +/* Data for android_dismiss_menu. */ + +struct android_dismiss_menu_data +{ + /* The menu object. */ + jobject menu; + + /* The window object. */ + jobject window; +}; + +/* Cancel the context menu passed in POINTER. Also, clear + popup_activated_flag. */ + +static void +android_dismiss_menu (void *pointer) +{ + struct android_dismiss_menu_data *data; + + data = pointer; + (*android_java_env)->CallVoidMethod (android_java_env, + data->menu, + menu_class.dismiss, + data->window); + popup_activated_flag = 0; +} + +/* Recursively process events until a ANDROID_CONTEXT_MENU event + arrives. Then, return the item ID specified in the event in + *ID. */ + +static void +android_process_events_for_menu (int *id) +{ + /* Set menu_event_id to -1; handle_one_android_event will set it to + the event ID upon receiving a context menu event. This can cause + a non-local exit. */ + x_display_list->menu_event_id = -1; + + /* Now wait for the menu event ID to change. */ + while (x_display_list->menu_event_id == -1) + { + /* Wait for events to become available. */ + android_wait_event (); + + /* Process pending signals. */ + process_pending_signals (); + + /* Maybe quit. */ + maybe_quit (); + } + + /* Return the ID. */ + *id = x_display_list->menu_event_id; +} + Lisp_Object android_menu_show (struct frame *f, int x, int y, int menuflags, Lisp_Object title, const char **error_name) @@ -140,11 +198,13 @@ 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; + Lisp_Object item_name, enable, def, tem; jmethodID method; jobject store; bool rc; jobject window; + int id, item_id; + struct android_dismiss_menu_data data; count = SPECPDL_INDEX (); @@ -266,6 +326,12 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, ; else { + /* Compute the item ID. This is the index of value. + Make sure it doesn't overflow. */ + + if (!INT_ADD_OK (0, i + MENU_ITEMS_ITEM_VALUE, &item_id)) + memory_full (i + MENU_ITEMS_ITEM_VALUE * sizeof (Lisp_Object)); + /* Add this menu item with the appropriate state. */ title_string = (!NILP (item_name) @@ -274,7 +340,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, (*android_java_env)->CallVoidMethod (android_java_env, current_context_menu, menu_class.add_item, - (jint) 1, + (jint) item_id, title_string, (jboolean) !NILP (enable)); android_exception_check (); @@ -295,6 +361,7 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, ANDROID_HANDLE_WINDOW); rc = (*android_java_env)->CallBooleanMethod (android_java_env, context_menu, + menu_class.display, window, (jint) x, (jint) y); android_exception_check (); @@ -303,25 +370,54 @@ android_menu_show (struct frame *f, int x, int y, int menuflags, /* This means displaying the menu failed. */ goto finish; -#if 0 - record_unwind_protect_ptr (android_dismiss_menu, &context_menu); + /* Make sure the context menu is always dismissed. */ + data.menu = context_menu; + data.window = window; + record_unwind_protect_ptr (android_dismiss_menu, &data); - /* Otherwise, loop waiting for the menu event to arrive. */ + /* Next, process events waiting for something to be selected. */ + popup_activated_flag = 1; + unblock_input (); android_process_events_for_menu (&id); + block_input (); if (!id) /* This means no menu item was selected. */ goto finish; -#endif + /* id is an index into menu_items. Check that it remains + valid. */ + if (id >= ASIZE (menu_items)) + goto finish; + + /* Now return the menu item at that location. */ + tem = AREF (menu_items, id); + unblock_input (); + return unbind_to (count, tem); finish: unblock_input (); return unbind_to (count, Qnil); } +#else + +int +popup_activated (void) +{ + return 0; +} + #endif +DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, + Smenu_or_popup_active_p, 0, 0, 0, + doc: /* SKIP: real doc in xfns.c. */) + (void) +{ + return (popup_activated ()) ? Qt : Qnil; +} + void init_androidmenu (void) { @@ -329,3 +425,9 @@ init_androidmenu (void) android_init_emacs_context_menu (); #endif } + +void +syms_of_androidmenu (void) +{ + defsubr (&Smenu_or_popup_active_p); +} diff --git a/src/androidterm.c b/src/androidterm.c index 002d39af707..4017fec60a5 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -1125,6 +1125,14 @@ handle_one_android_event (struct android_display_info *dpyinfo, XSETFRAME (inev.ie.frame_or_window, any); goto OTHER; + /* Context menu handling. */ + case ANDROID_CONTEXT_MENU: + + if (dpyinfo->menu_event_id == -1) + dpyinfo->menu_event_id = event->menu.menu_event_id; + + goto OTHER; + default: goto OTHER; } diff --git a/src/androidterm.h b/src/androidterm.h index e83e32a5854..9aa09877196 100644 --- a/src/androidterm.h +++ b/src/androidterm.h @@ -132,6 +132,10 @@ struct android_display_info /* The time of the last mouse movement. */ Time last_mouse_movement_time; + + /* ID of the last menu event received. -1 means Emacs is waiting + for a context menu event. */ + int menu_event_id; }; /* Structure representing a single tool (finger or stylus) pressed @@ -407,6 +411,7 @@ extern void android_finalize_font_entity (struct font_entity *); extern Lisp_Object android_menu_show (struct frame *, int, int, int, Lisp_Object, const char **); extern void init_androidmenu (void); +extern void syms_of_androidmenu (void); /* Defined in sfntfont-android.c. */ diff --git a/src/emacs.c b/src/emacs.c index 8f5be53aad9..f4973c70610 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -2397,6 +2397,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem #ifdef HAVE_ANDROID syms_of_androidterm (); syms_of_androidfns (); + syms_of_androidmenu (); syms_of_fontset (); #if !defined ANDROID_STUBIFY syms_of_androidfont (); diff --git a/src/xdisp.c b/src/xdisp.c index b9ee102af26..262a823f899 100644 --- a/src/xdisp.c +++ b/src/xdisp.c @@ -34959,7 +34959,8 @@ note_mouse_highlight (struct frame *f, int x, int y) struct buffer *b; /* When a menu is active, don't highlight because this looks odd. */ -#if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) || defined (MSDOS) +#if defined (HAVE_X_WINDOWS) || defined (HAVE_NS) || defined (MSDOS) \ + || defined (HAVE_ANDROID) if (popup_activated ()) return; #endif