root/src/androidmenu.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. popup_activated
  2. android_init_emacs_context_menu
  3. android_unwind_local_frame
  4. android_push_local_frame
  5. android_dismiss_menu
  6. android_process_events_for_menu
  7. android_free_subprefixes
  8. android_menu_show
  9. android_init_emacs_dialog
  10. android_dialog_show
  11. android_popup_dialog
  12. popup_activated
  13. DEFUN
  14. init_androidmenu
  15. syms_of_androidmenu

     1 /* Communication module for Android terminals.
     2 
     3 Copyright (C) 2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU Emacs is free software: you can redistribute it and/or modify
     8 it under the terms of the GNU General Public License as published by
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 #include <config.h>
    21 
    22 #include "lisp.h"
    23 #include "androidterm.h"
    24 #include "android.h"
    25 #include "blockinput.h"
    26 #include "keyboard.h"
    27 #include "menu.h"
    28 
    29 #ifndef ANDROID_STUBIFY
    30 
    31 #include <android/log.h>
    32 
    33 /* Flag indicating whether or not a popup menu has been posted and not
    34    yet popped down.  */
    35 
    36 static int popup_activated_flag;
    37 
    38 /* Serial number used to identify which context menu events are
    39    associated with the context menu currently being displayed.  */
    40 
    41 unsigned int current_menu_serial;
    42 
    43 int
    44 popup_activated (void)
    45 {
    46   return popup_activated_flag;
    47 }
    48 
    49 
    50 
    51 /* Toolkit menu implementation.  */
    52 
    53 /* Structure describing the EmacsContextMenu class.  */
    54 
    55 struct android_emacs_context_menu
    56 {
    57   jclass class;
    58   jmethodID create_context_menu;
    59   jmethodID add_item;
    60   jmethodID add_submenu;
    61   jmethodID add_pane;
    62   jmethodID parent;
    63   jmethodID display;
    64   jmethodID dismiss;
    65 };
    66 
    67 /* Identifiers associated with the EmacsContextMenu class.  */
    68 static struct android_emacs_context_menu menu_class;
    69 
    70 static void
    71 android_init_emacs_context_menu (void)
    72 {
    73   jclass old;
    74 
    75   menu_class.class
    76     = (*android_java_env)->FindClass (android_java_env,
    77                                       "org/gnu/emacs/EmacsContextMenu");
    78   eassert (menu_class.class);
    79 
    80   old = menu_class.class;
    81   menu_class.class
    82     = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
    83                                                   (jobject) old);
    84   ANDROID_DELETE_LOCAL_REF (old);
    85 
    86   if (!menu_class.class)
    87     emacs_abort ();
    88 
    89 #define FIND_METHOD(c_name, name, signature)                    \
    90   menu_class.c_name                                             \
    91     = (*android_java_env)->GetMethodID (android_java_env,       \
    92                                         menu_class.class,       \
    93                                         name, signature);       \
    94   eassert (menu_class.c_name);
    95 
    96 #define FIND_METHOD_STATIC(c_name, name, signature)             \
    97   menu_class.c_name                                             \
    98     = (*android_java_env)->GetStaticMethodID (android_java_env, \
    99                                               menu_class.class, \
   100                                               name, signature); \
   101   eassert (menu_class.c_name);
   102 
   103   FIND_METHOD_STATIC (create_context_menu, "createContextMenu",
   104                       "(Ljava/lang/String;)"
   105                       "Lorg/gnu/emacs/EmacsContextMenu;");
   106 
   107   FIND_METHOD (add_item, "addItem", "(ILjava/lang/String;ZZZ"
   108                "Ljava/lang/String;Z)V");
   109   FIND_METHOD (add_submenu, "addSubmenu", "(Ljava/lang/String;"
   110                "Ljava/lang/String;)"
   111                "Lorg/gnu/emacs/EmacsContextMenu;");
   112   FIND_METHOD (add_pane, "addPane", "(Ljava/lang/String;)V");
   113   FIND_METHOD (parent, "parent", "()Lorg/gnu/emacs/EmacsContextMenu;");
   114   FIND_METHOD (display, "display", "(Lorg/gnu/emacs/EmacsWindow;III)Z");
   115   FIND_METHOD (dismiss, "dismiss", "(Lorg/gnu/emacs/EmacsWindow;)V");
   116 
   117 #undef FIND_METHOD
   118 #undef FIND_METHOD_STATIC
   119 }
   120 
   121 static void
   122 android_unwind_local_frame (void)
   123 {
   124   (*android_java_env)->PopLocalFrame (android_java_env, NULL);
   125 }
   126 
   127 /* Push a local reference frame to the JVM stack and record it on the
   128    specpdl.  Release local references created within that frame when
   129    the specpdl is unwound past where it is after returning.  */
   130 
   131 static void
   132 android_push_local_frame (void)
   133 {
   134   int rc;
   135 
   136   rc = (*android_java_env)->PushLocalFrame (android_java_env, 30);
   137 
   138   /* This means the JVM ran out of memory.  */
   139   if (rc < 1)
   140     android_exception_check ();
   141 
   142   record_unwind_protect_void (android_unwind_local_frame);
   143 }
   144 
   145 /* Data for android_dismiss_menu.  */
   146 
   147 struct android_dismiss_menu_data
   148 {
   149   /* The menu object.  */
   150   jobject menu;
   151 
   152   /* The window object.  */
   153   jobject window;
   154 };
   155 
   156 /* Cancel the context menu passed in POINTER.  Also, clear
   157    popup_activated_flag.  */
   158 
   159 static void
   160 android_dismiss_menu (void *pointer)
   161 {
   162   struct android_dismiss_menu_data *data;
   163 
   164   data = pointer;
   165   (*android_java_env)->CallVoidMethod (android_java_env,
   166                                        data->menu,
   167                                        menu_class.dismiss,
   168                                        data->window);
   169   popup_activated_flag = 0;
   170 }
   171 
   172 /* Recursively process events until a ANDROID_CONTEXT_MENU event
   173    arrives.  Then, return the item ID specified in the event in
   174    *ID.  */
   175 
   176 static void
   177 android_process_events_for_menu (int *id)
   178 {
   179   int blocked;
   180 
   181   /* Set menu_event_id to -1; handle_one_android_event will set it to
   182      the event ID upon receiving a context menu event.  This can cause
   183      a non-local exit.  */
   184   x_display_list->menu_event_id = -1;
   185 
   186   /* Unblock input completely.  */
   187   blocked = interrupt_input_blocked;
   188   totally_unblock_input ();
   189 
   190   /* Now wait for the menu event ID to change.  */
   191   while (x_display_list->menu_event_id == -1)
   192     {
   193       /* Wait for events to become available.  */
   194       android_wait_event ();
   195 
   196       /* Process pending signals.  */
   197       process_pending_signals ();
   198 
   199       /* Maybe quit.  This is important because the framework (on
   200          Android 4.0.3) can sometimes fail to deliver context menu
   201          closed events if a submenu was opened, and the user still
   202          needs to be able to quit.  */
   203       maybe_quit ();
   204     }
   205 
   206   /* Restore the input block.  */
   207   interrupt_input_blocked = blocked;
   208 
   209   /* Return the ID.  */
   210   *id = x_display_list->menu_event_id;
   211 }
   212 
   213 /* Structure describing a ``subprefix'' in the menu.  */
   214 
   215 struct android_menu_subprefix
   216 {
   217   /* The subprefix above.  */
   218   struct android_menu_subprefix *last;
   219 
   220   /* The subprefix itself.  */
   221   Lisp_Object subprefix;
   222 };
   223 
   224 /* Free the subprefixes starting from *DATA.  */
   225 
   226 static void
   227 android_free_subprefixes (void *data)
   228 {
   229   struct android_menu_subprefix **head, *subprefix;
   230 
   231   head = data;
   232 
   233   while (*head)
   234     {
   235       subprefix = *head;
   236       *head = subprefix->last;
   237 
   238       xfree (subprefix);
   239     }
   240 }
   241 
   242 Lisp_Object
   243 android_menu_show (struct frame *f, int x, int y, int menuflags,
   244                    Lisp_Object title, const char **error_name)
   245 {
   246   jobject context_menu, current_context_menu;
   247   jobject title_string, help_string, temp;
   248   size_t i;
   249   Lisp_Object pane_name, prefix;
   250   const char *pane_string;
   251   specpdl_ref count, count1;
   252   Lisp_Object item_name, enable, def, tem, entry, type, selected;
   253   Lisp_Object help;
   254   jmethodID method;
   255   jobject store;
   256   bool rc;
   257   jobject window;
   258   int id, item_id, submenu_depth;
   259   struct android_dismiss_menu_data data;
   260   struct android_menu_subprefix *subprefix, *temp_subprefix;
   261   struct android_menu_subprefix *subprefix_1;
   262   bool checkmark;
   263   unsigned int serial;
   264 
   265   count = SPECPDL_INDEX ();
   266   serial = ++current_menu_serial;
   267 
   268   block_input ();
   269 
   270   /* Push the first local frame.  */
   271   android_push_local_frame ();
   272 
   273   /* Set title_string to a Java string containing TITLE if non-nil.
   274      If the menu consists of more than one pane, replace the title
   275      with the pane header item so that the menu looks consistent.  */
   276 
   277   title_string = NULL;
   278   if (STRINGP (title) && menu_items_n_panes < 2)
   279     title_string = android_build_string (title);
   280 
   281   /* Push the first local frame for the context menu.  */
   282   method = menu_class.create_context_menu;
   283   current_context_menu = context_menu
   284     = (*android_java_env)->CallStaticObjectMethod (android_java_env,
   285                                                    menu_class.class,
   286                                                    method,
   287                                                    title_string);
   288 
   289   /* Delete the unused title reference.  */
   290 
   291   if (title_string)
   292     ANDROID_DELETE_LOCAL_REF (title_string);
   293 
   294   /* Push the second local frame for temporaries.  */
   295   count1 = SPECPDL_INDEX ();
   296   android_push_local_frame ();
   297 
   298   /* Iterate over the menu.  */
   299   i = 0, submenu_depth = 0;
   300 
   301   while (i < menu_items_used)
   302     {
   303       if (NILP (AREF (menu_items, i)))
   304         {
   305           /* This is the start of a new submenu.  However, it can be
   306              ignored here.  */
   307           i += 1;
   308           submenu_depth += 1;
   309         }
   310       else if (EQ (AREF (menu_items, i), Qlambda))
   311         {
   312           /* This is the end of a submenu.  Go back to the previous
   313              context menu.  */
   314           store = current_context_menu;
   315           current_context_menu
   316             = (*android_java_env)->CallObjectMethod (android_java_env,
   317                                                      current_context_menu,
   318                                                      menu_class.parent);
   319           android_exception_check ();
   320 
   321           if (store != context_menu)
   322             ANDROID_DELETE_LOCAL_REF (store);
   323           i += 1;
   324           submenu_depth -= 1;
   325 
   326           if (!current_context_menu || submenu_depth < 0)
   327             {
   328               __android_log_print (ANDROID_LOG_FATAL, __func__,
   329                                    "unbalanced submenu pop in menu_items");
   330               emacs_abort ();
   331             }
   332         }
   333       else if (EQ (AREF (menu_items, i), Qt)
   334                && submenu_depth != 0)
   335         i += MENU_ITEMS_PANE_LENGTH;
   336       else if (EQ (AREF (menu_items, i), Qquote))
   337         i += 1;
   338       else if (EQ (AREF (menu_items, i), Qt))
   339         {
   340           /* If the menu contains a single pane, then the pane is
   341              actually TITLE.  Don't duplicate the text within the
   342              context menu title.  */
   343 
   344           if (menu_items_n_panes < 2)
   345             goto next_item;
   346 
   347           /* This is a new pane.  Switch back to the topmost context
   348              menu.  */
   349           if (current_context_menu != context_menu)
   350             ANDROID_DELETE_LOCAL_REF (current_context_menu);
   351           current_context_menu = context_menu;
   352 
   353           /* Now figure out the title of this pane.  */
   354           pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
   355           prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
   356           pane_string = (NILP (pane_name)
   357                          ? "" : SSDATA (pane_name));
   358           if ((menuflags & MENU_KEYMAPS) && !NILP (prefix))
   359             pane_string++;
   360 
   361           /* Add the pane.  */
   362           temp = (*android_java_env)->NewStringUTF (android_java_env,
   363                                                     pane_string);
   364           android_exception_check ();
   365 
   366           (*android_java_env)->CallVoidMethod (android_java_env,
   367                                                current_context_menu,
   368                                                menu_class.add_pane,
   369                                                temp);
   370           android_exception_check ();
   371           ANDROID_DELETE_LOCAL_REF (temp);
   372 
   373         next_item:
   374           i += MENU_ITEMS_PANE_LENGTH;
   375         }
   376       else
   377         {
   378           item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
   379           enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
   380           def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION);
   381           type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE);
   382           selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED);
   383           help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
   384 
   385           /* This is an actual menu item (or submenu).  Add it to the
   386              menu.  */
   387 
   388           if (i + MENU_ITEMS_ITEM_LENGTH < menu_items_used
   389               && NILP (AREF (menu_items, i + MENU_ITEMS_ITEM_LENGTH)))
   390             {
   391               /* This is a submenu.  Add it.  */
   392               title_string = (!NILP (item_name)
   393                               ? android_build_string (item_name)
   394                               : NULL);
   395               help_string = NULL;
   396 
   397               /* Menu items can have tool tips on Android 26 and
   398                  later.  In this case, set it to the help string.  */
   399 
   400               if (android_get_current_api_level () >= 26
   401                   && STRINGP (help))
   402                 help_string = android_build_string (help);
   403 
   404               store = current_context_menu;
   405               current_context_menu
   406                 = (*android_java_env)->CallObjectMethod (android_java_env,
   407                                                          current_context_menu,
   408                                                          menu_class.add_submenu,
   409                                                          title_string,
   410                                                          help_string);
   411               android_exception_check ();
   412 
   413               if (store != context_menu)
   414                 ANDROID_DELETE_LOCAL_REF (store);
   415 
   416               if (title_string)
   417                 ANDROID_DELETE_LOCAL_REF (title_string);
   418 
   419               if (help_string)
   420                 ANDROID_DELETE_LOCAL_REF (help_string);
   421             }
   422           else if (NILP (def) && menu_separator_name_p (SSDATA (item_name)))
   423             /* Ignore this separator item.  */
   424             ;
   425           else
   426             {
   427               /* Compute the item ID.  This is the index of value.
   428                  Make sure it doesn't overflow.  */
   429 
   430               if (!INT_ADD_OK (0, i + MENU_ITEMS_ITEM_VALUE, &item_id))
   431                 memory_full (i + MENU_ITEMS_ITEM_VALUE * sizeof (Lisp_Object));
   432 
   433               /* Add this menu item with the appropriate state.  */
   434 
   435               title_string = (!NILP (item_name)
   436                               ? android_build_string (item_name)
   437                               : NULL);
   438               help_string = NULL;
   439 
   440               /* Menu items can have tool tips on Android 26 and
   441                  later.  In this case, set it to the help string.  */
   442 
   443               if (android_get_current_api_level () >= 26
   444                   && STRINGP (help))
   445                 help_string = android_build_string (help);
   446 
   447               /* Determine whether or not to display a check box.  */
   448 
   449               checkmark = (EQ (type, QCtoggle)
   450                            || EQ (type, QCradio));
   451 
   452               (*android_java_env)->CallVoidMethod (android_java_env,
   453                                                    current_context_menu,
   454                                                    menu_class.add_item,
   455                                                    (jint) item_id,
   456                                                    title_string,
   457                                                    (jboolean) !NILP (enable),
   458                                                    (jboolean) checkmark,
   459                                                    (jboolean) !NILP (selected),
   460                                                    help_string,
   461                                                    (jboolean) (EQ (type,
   462                                                                    QCradio)));
   463               android_exception_check ();
   464 
   465               if (title_string)
   466                 ANDROID_DELETE_LOCAL_REF (title_string);
   467 
   468               if (help_string)
   469                 ANDROID_DELETE_LOCAL_REF (help_string);
   470             }
   471 
   472           i += MENU_ITEMS_ITEM_LENGTH;
   473         }
   474     }
   475 
   476   /* The menu has now been built.  Pop the second local frame.  */
   477   unbind_to (count1, Qnil);
   478 
   479   /* Now, display the context menu.  */
   480   window = android_resolve_handle (FRAME_ANDROID_WINDOW (f),
   481                                    ANDROID_HANDLE_WINDOW);
   482   rc = (*android_java_env)->CallBooleanMethod (android_java_env,
   483                                                context_menu,
   484                                                menu_class.display,
   485                                                window, (jint) x,
   486                                                (jint) y,
   487                                                (jint) serial);
   488   android_exception_check ();
   489 
   490   if (!rc)
   491     /* This means displaying the menu failed.  */
   492     goto finish;
   493 
   494   /* Make sure the context menu is always dismissed.  */
   495   data.menu = context_menu;
   496   data.window = window;
   497   record_unwind_protect_ptr (android_dismiss_menu, &data);
   498 
   499   /* Next, process events waiting for something to be selected.  */
   500   popup_activated_flag = 1;
   501   android_process_events_for_menu (&id);
   502 
   503   if (!id)
   504     /* This means no menu item was selected.  */
   505     goto finish;
   506 
   507   /* This means the id is invalid.  */
   508   if (id >= ASIZE (menu_items))
   509     goto finish;
   510 
   511   /* Now return the menu item at that location.  */
   512   tem = Qnil;
   513   subprefix = NULL;
   514   record_unwind_protect_ptr (android_free_subprefixes, &subprefix);
   515 
   516   /* Find the selected item, and its pane, to return
   517      the proper value.  */
   518 
   519   prefix = entry = Qnil;
   520   i = 0;
   521   while (i < menu_items_used)
   522     {
   523       if (NILP (AREF (menu_items, i)))
   524         {
   525           temp_subprefix = xmalloc (sizeof *temp_subprefix);
   526           temp_subprefix->last = subprefix;
   527           subprefix = temp_subprefix;
   528           subprefix->subprefix = prefix;
   529 
   530           prefix = entry;
   531           i++;
   532         }
   533       else if (EQ (AREF (menu_items, i), Qlambda))
   534         {
   535           prefix = subprefix->subprefix;
   536           temp_subprefix = subprefix->last;
   537           xfree (subprefix);
   538           subprefix = temp_subprefix;
   539 
   540           i++;
   541         }
   542       else if (EQ (AREF (menu_items, i), Qt))
   543         {
   544           prefix
   545             = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
   546           i += MENU_ITEMS_PANE_LENGTH;
   547         }
   548       /* Ignore a nil in the item list.
   549          It's meaningful only for dialog boxes.  */
   550       else if (EQ (AREF (menu_items, i), Qquote))
   551         i += 1;
   552       else
   553         {
   554           entry = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
   555 
   556           if (i + MENU_ITEMS_ITEM_VALUE == id)
   557             {
   558               if (menuflags & MENU_KEYMAPS)
   559                 {
   560                   entry = list1 (entry);
   561 
   562                   if (!NILP (prefix))
   563                     entry = Fcons (prefix, entry);
   564 
   565                   for (subprefix_1 = subprefix; subprefix_1;
   566                        subprefix_1 = subprefix_1->last)
   567                     if (!NILP (subprefix_1->subprefix))
   568                       entry = Fcons (subprefix_1->subprefix, entry);
   569                 }
   570 
   571               tem = entry;
   572             }
   573           i += MENU_ITEMS_ITEM_LENGTH;
   574         }
   575     }
   576 
   577   unblock_input ();
   578   return unbind_to (count, tem);
   579 
   580  finish:
   581   unblock_input ();
   582   return unbind_to (count, Qnil);
   583 }
   584 
   585 
   586 
   587 /* Toolkit dialog implementation.  */
   588 
   589 /* Structure describing the EmacsDialog class.  */
   590 
   591 struct android_emacs_dialog
   592 {
   593   jclass class;
   594   jmethodID create_dialog;
   595   jmethodID add_button;
   596   jmethodID display;
   597 };
   598 
   599 /* Identifiers associated with the EmacsDialog class.  */
   600 static struct android_emacs_dialog dialog_class;
   601 
   602 static void
   603 android_init_emacs_dialog (void)
   604 {
   605   jclass old;
   606 
   607   dialog_class.class
   608     = (*android_java_env)->FindClass (android_java_env,
   609                                       "org/gnu/emacs/EmacsDialog");
   610   eassert (dialog_class.class);
   611 
   612   old = dialog_class.class;
   613   dialog_class.class
   614     = (jclass) (*android_java_env)->NewGlobalRef (android_java_env,
   615                                                   (jobject) old);
   616   ANDROID_DELETE_LOCAL_REF (old);
   617 
   618   if (!dialog_class.class)
   619     emacs_abort ();
   620 
   621 #define FIND_METHOD(c_name, name, signature)                    \
   622   dialog_class.c_name                                           \
   623     = (*android_java_env)->GetMethodID (android_java_env,       \
   624                                         dialog_class.class,     \
   625                                         name, signature);       \
   626   eassert (dialog_class.c_name);
   627 
   628 #define FIND_METHOD_STATIC(c_name, name, signature)                     \
   629   dialog_class.c_name                                                   \
   630     = (*android_java_env)->GetStaticMethodID (android_java_env,         \
   631                                               dialog_class.class,       \
   632                                               name, signature);         \
   633 
   634   FIND_METHOD_STATIC (create_dialog, "createDialog", "(Ljava/lang/String;"
   635                       "Ljava/lang/String;I)Lorg/gnu/emacs/EmacsDialog;");
   636   FIND_METHOD (add_button, "addButton", "(Ljava/lang/String;IZ)V");
   637   FIND_METHOD (display, "display", "()Z");
   638 
   639 #undef FIND_METHOD
   640 #undef FIND_METHOD_STATIC
   641 }
   642 
   643 static Lisp_Object
   644 android_dialog_show (struct frame *f, Lisp_Object title,
   645                      Lisp_Object header, const char **error_name)
   646 {
   647   specpdl_ref count;
   648   jobject dialog, java_header, java_title, temp;
   649   size_t i;
   650   Lisp_Object item_name, enable, entry;
   651   bool rc;
   652   int id;
   653   jmethodID method;
   654   unsigned int serial;
   655 
   656   /* Generate a unique ID for events from this dialog box.  */
   657   serial = ++current_menu_serial;
   658 
   659   if (menu_items_n_panes > 1)
   660     {
   661       *error_name = "Multiple panes in dialog box";
   662       return Qnil;
   663     }
   664 
   665   /* Do the initial setup.  */
   666   count = SPECPDL_INDEX ();
   667   *error_name = NULL;
   668 
   669   android_push_local_frame ();
   670 
   671   /* Figure out what header to use.  */
   672   java_header = (!NILP (header)
   673                  ? android_build_jstring ("Information")
   674                  : android_build_jstring ("Question"));
   675 
   676   /* And the title.  */
   677   java_title = android_build_string (title);
   678 
   679   /* Now create the dialog.  */
   680   method = dialog_class.create_dialog;
   681   dialog = (*android_java_env)->CallStaticObjectMethod (android_java_env,
   682                                                         dialog_class.class,
   683                                                         method, java_header,
   684                                                         java_title,
   685                                                         (jint) serial);
   686   android_exception_check ();
   687 
   688   /* Delete now unused local references.  */
   689   if (java_header)
   690     ANDROID_DELETE_LOCAL_REF (java_header);
   691   ANDROID_DELETE_LOCAL_REF (java_title);
   692 
   693   /* Create the buttons.  */
   694   i = MENU_ITEMS_PANE_LENGTH;
   695   while (i < menu_items_used)
   696     {
   697       item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME);
   698       enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE);
   699 
   700       /* Verify that there is no submenu here.  */
   701 
   702       if (NILP (item_name))
   703         {
   704           *error_name = "Submenu in dialog items";
   705           return unbind_to (count, Qnil);
   706         }
   707 
   708       /* Skip past boundaries between buttons on different sides.  The
   709          Android toolkit is too silly to understand this
   710          distinction.  */
   711 
   712       if (EQ (item_name, Qquote))
   713         ++i;
   714       else
   715         {
   716           /* Make sure i is within bounds.  */
   717           if (i > TYPE_MAXIMUM (jint))
   718             {
   719               *error_name = "Dialog box too big";
   720               return unbind_to (count, Qnil);
   721             }
   722 
   723           /* Add the button.  */
   724           temp = android_build_string (item_name);
   725           (*android_java_env)->CallVoidMethod (android_java_env,
   726                                                dialog,
   727                                                dialog_class.add_button,
   728                                                temp, (jint) i,
   729                                                (jboolean) NILP (enable));
   730           android_exception_check ();
   731           ANDROID_DELETE_LOCAL_REF (temp);
   732           i += MENU_ITEMS_ITEM_LENGTH;
   733         }
   734     }
   735 
   736   /* The dialog is now built.  Run it.  */
   737   rc = (*android_java_env)->CallBooleanMethod (android_java_env,
   738                                                dialog,
   739                                                dialog_class.display);
   740   android_exception_check ();
   741 
   742   if (!rc)
   743     quit ();
   744 
   745   /* Wait for the menu ID to arrive.  */
   746   android_process_events_for_menu (&id);
   747 
   748   if (!id)
   749     quit ();
   750 
   751   /* Find the selected item, and its pane, to return
   752      the proper value.  */
   753   i = 0;
   754   while (i < menu_items_used)
   755     {
   756       if (EQ (AREF (menu_items, i), Qt))
   757         i += MENU_ITEMS_PANE_LENGTH;
   758       else if (EQ (AREF (menu_items, i), Qquote))
   759         /* This is the boundary between left-side elts and right-side
   760            elts.  */
   761         ++i;
   762       else
   763         {
   764           entry = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
   765 
   766           if (id == i)
   767             return entry;
   768 
   769           i += MENU_ITEMS_ITEM_LENGTH;
   770         }
   771     }
   772 
   773   return Qnil;
   774 }
   775 
   776 Lisp_Object
   777 android_popup_dialog (struct frame *f, Lisp_Object header,
   778                       Lisp_Object contents)
   779 {
   780   Lisp_Object title;
   781   const char *error_name;
   782   Lisp_Object selection;
   783   specpdl_ref specpdl_count = SPECPDL_INDEX ();
   784 
   785   check_window_system (f);
   786 
   787   /* Decode the dialog items from what was specified.  */
   788   title = Fcar (contents);
   789   CHECK_STRING (title);
   790   record_unwind_protect_void (unuse_menu_items);
   791 
   792   if (NILP (Fcar (Fcdr (contents))))
   793     /* No buttons specified, add an "Ok" button so users can pop down
   794        the dialog.  */
   795     contents = list2 (title, Fcons (build_string ("Ok"), Qt));
   796 
   797   list_of_panes (list1 (contents));
   798 
   799   /* Display them in a dialog box.  */
   800   block_input ();
   801   selection = android_dialog_show (f, title, header, &error_name);
   802   unblock_input ();
   803 
   804   unbind_to (specpdl_count, Qnil);
   805   discard_menu_items ();
   806 
   807   if (error_name)
   808     error ("%s", error_name);
   809 
   810   return selection;
   811 }
   812 
   813 #else
   814 
   815 int
   816 popup_activated (void)
   817 {
   818   return 0;
   819 }
   820 
   821 #endif
   822 
   823 DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p,
   824        Smenu_or_popup_active_p, 0, 0, 0,
   825        doc: /* SKIP: real doc in xfns.c.  */)
   826   (void)
   827 {
   828   return (popup_activated ()) ? Qt : Qnil;
   829 }
   830 
   831 void
   832 init_androidmenu (void)
   833 {
   834 #ifndef ANDROID_STUBIFY
   835   android_init_emacs_context_menu ();
   836   android_init_emacs_dialog ();
   837 #endif
   838 }
   839 
   840 void
   841 syms_of_androidmenu (void)
   842 {
   843   defsubr (&Smenu_or_popup_active_p);
   844 }

/* [<][>][^][v][top][bottom][index][help] */