[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
feature/android 6e2bc91d92 1/2: Implement toolkit menus on Android
From: |
Po Lu |
Subject: |
feature/android 6e2bc91d92 1/2: Implement toolkit menus on Android |
Date: |
Sat, 14 Jan 2023 22:57:40 -0500 (EST) |
branch: feature/android
commit 6e2bc91d924fbeb0ad5728e0424eabc905c0d366
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>
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.
---
java/org/gnu/emacs/EmacsActivity.java | 15 ++++
java/org/gnu/emacs/EmacsContextMenu.java | 64 +++++++++++++++--
java/org/gnu/emacs/EmacsNative.java | 3 +
java/org/gnu/emacs/EmacsView.java | 2 +
src/android.c | 42 +++++++++++-
src/android.h | 1 +
src/androidgui.h | 16 +++++
src/androidmenu.c | 114 +++++++++++++++++++++++++++++--
src/androidterm.c | 8 +++
src/androidterm.h | 5 ++
src/emacs.c | 1 +
src/xdisp.c | 3 +-
12 files changed, 258 insertions(+), 16 deletions(-)
diff --git a/java/org/gnu/emacs/EmacsActivity.java
b/java/org/gnu/emacs/EmacsActivity.java
index 4cd286d1e8..4b96a37698 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 8d7ae08b25..02dd1c7efa 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<Item> 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 a11e509cd7..4a80f88edc 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 1391f630be..445d8ffa02 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 5e5e28c60c..ed162a903b 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 98f2494e9a..e68e0a51fb 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 8450a1f637..0e075fda95 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 0f0c6f4ef1..7522f9c5a5 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 002d39af70..4017fec60a 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 e83e32a585..9aa0987719 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 8f5be53aad..f4973c7061 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 b9ee102af2..262a823f89 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