mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2026-09-10 07:46:51 -04:00
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.
This commit is contained in:
parent
c02a7b2ff4
commit
6e2bc91d92
|
|
@ -30,6 +30,7 @@
|
|||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
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 @@ Android menu, which can be turned into a popup (or other kind of)
|
|||
|
||||
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 @@ private class Item
|
|||
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 @@ private class Item
|
|||
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 @@ private class Item
|
|||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ public static native void sendWheel (short window, int x, int y,
|
|||
/* 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");
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ else if (child.getVisibility () != GONE)
|
|||
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 @@ else if (child.getVisibility () != GONE)
|
|||
+ " popupActive set");
|
||||
|
||||
contextMenu = null;
|
||||
popupActive = false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue