emacs-diffs
[Top][All Lists]
Advanced

[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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]