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;
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);
{
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)
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.SubMenu;
import android.util.Log;
/* 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;
{
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);
{
Intent intent;
MenuItem menuItem;
- Menu submenu;
+ SubMenu submenu;
for (Item item : menuItems)
{
/* 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
{
public EmacsContextMenu
parent ()
{
- return parent;
+ return this.parent;
}
/* Like display, but does the actual work and runs in the main
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);
}
}
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);
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.
public static native void setEmacsParams (AssetManager assetManager,
String filesDir,
String libDir,
+ String cacheDir,
float pixelDensityX,
float pixelDensityY,
EmacsService emacsService);
{
AssetManager manager;
Context app_context;
- String filesDir, libDir;
+ String filesDir, libDir, cacheDir;
double pixelDensityX;
double pixelDensityY;
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);
{
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 ();
+ }
+ }
+ }
};
jmethodID get_screen_height;
jmethodID detect_mouse;
jmethodID name_keysym;
+ jmethodID sync;
};
struct android_emacs_pixmap
/* 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;
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)
(*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);
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
}
/* 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__,
ANDROID_DELETE_LOCAL_REF (string);
}
+void
+android_sync (void)
+{
+ (*android_java_env)->CallVoidMethod (android_java_env,
+ emacs_service,
+ service_class.sync);
+ android_exception_check ();
+}
+
\f
#undef faccessat
#include "blockinput.h"
#include "keyboard.h"
#include "buffer.h"
+#include "androidgui.h"
#ifndef ANDROID_STUBIFY
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);
extern void android_map_raised (android_window);
extern void android_translate_coordinates (android_window, int,
int, int *, int *);
+extern void android_sync (void);
#endif
#ifndef ANDROID_STUBIFY
+#include <android/log.h>
+
/* Flag indicating whether or not a popup menu has been posted and not
yet popped down. */
*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)
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 ();
android_push_local_frame ();
/* Iterate over the menu. */
- i = 0;
+ i = 0, submenu_depth = 0;
while (i < menu_items_used)
{
/* 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))
{
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))
/* 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)
= (*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)
/* 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);
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)
}
}
-#ifdef HAVE_EXT_MENU_BAR
+#if defined HAVE_EXT_MENU_BAR || defined HAVE_ANDROID
/* Begin a submenu. */
menu_items_submenu_depth--;
}
-#endif /* HAVE_EXT_MENU_BAR */
+#endif /* HAVE_EXT_MENU_BAR || HAVE_ANDROID */
/* Indicate boundary between left and right. */
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)))