From 14217e872409c762c9ff359b6fc79cdcd6f0f97b Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Sat, 16 Mar 2024 22:56:40 +0100 Subject: [PATCH] simple implementation of PopupMenu using GtkPopoverMenu --- meson.build | 1 + .../android_widget_PopupMenu.h | 45 ++++++ .../widgets/android_widget_PopupMenu.c | 50 +++++++ src/api-impl/android/widget/PopupMenu.java | 136 +++++++++++++++++- 4 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 src/api-impl-jni/generated_headers/android_widget_PopupMenu.h create mode 100644 src/api-impl-jni/widgets/android_widget_PopupMenu.c diff --git a/meson.build b/meson.build index f08786ac..3fdca679 100644 --- a/meson.build +++ b/meson.build @@ -117,6 +117,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/widgets/android_widget_EditText.c', 'src/api-impl-jni/widgets/android_widget_ImageButton.c', 'src/api-impl-jni/widgets/android_widget_ImageView.c', + 'src/api-impl-jni/widgets/android_widget_PopupMenu.c', 'src/api-impl-jni/widgets/android_widget_PopupWindow.c', 'src/api-impl-jni/widgets/android_widget_Progressbar.c', 'src/api-impl-jni/widgets/android_widget_RadioButton.c', diff --git a/src/api-impl-jni/generated_headers/android_widget_PopupMenu.h b/src/api-impl-jni/generated_headers/android_widget_PopupMenu.h new file mode 100644 index 00000000..57a7a5b3 --- /dev/null +++ b/src/api-impl-jni/generated_headers/android_widget_PopupMenu.h @@ -0,0 +1,45 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class android_widget_PopupMenu */ + +#ifndef _Included_android_widget_PopupMenu +#define _Included_android_widget_PopupMenu +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: android_widget_PopupMenu + * Method: native_init + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_android_widget_PopupMenu_native_1init + (JNIEnv *, jobject); + +/* + * Class: android_widget_PopupMenu + * Method: native_appendItem + * Signature: (JLjava/lang/String;I)V + */ +JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1appendItem + (JNIEnv *, jobject, jlong, jstring, jint); + +/* + * Class: android_widget_PopupMenu + * Method: native_buildPopover + * Signature: (J)J + */ +JNIEXPORT jlong JNICALL Java_android_widget_PopupMenu_native_1buildPopover + (JNIEnv *, jobject, jlong); + +/* + * Class: android_widget_PopupMenu + * Method: native_show + * Signature: (JJ)V + */ +JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1show + (JNIEnv *, jobject, jlong, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/api-impl-jni/widgets/android_widget_PopupMenu.c b/src/api-impl-jni/widgets/android_widget_PopupMenu.c new file mode 100644 index 00000000..e9b707d5 --- /dev/null +++ b/src/api-impl-jni/widgets/android_widget_PopupMenu.c @@ -0,0 +1,50 @@ +#include + +#include "../defines.h" +#include "../util.h" + +#include "../generated_headers/android_widget_PopupMenu.h" + +JNIEXPORT jlong JNICALL Java_android_widget_PopupMenu_native_1init(JNIEnv *env, jobject this) +{ + return _INTPTR(g_menu_new()); +} + +JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1appendItem(JNIEnv *env, jobject this, jlong menu_ptr, jstring title_jstr, jint id) +{ + const gchar *title = (*env)->GetStringUTFChars(env, title_jstr, NULL); + GMenuItem *item = g_menu_item_new(title, NULL); + (*env)->ReleaseStringUTFChars(env, title_jstr, title); + g_menu_item_set_action_and_target(item, "popupmenu.clicked", "i", id); + g_menu_append_item(G_MENU(_PTR(menu_ptr)), item); +} + +static void popupmenu_activated(GSimpleAction *action, GVariant *parameter, gpointer user_data) { + int id = g_variant_get_int32(parameter); + JNIEnv *env = get_jni_env(); + jobject this = (jobject)user_data; + jmethodID onMenuItemClick = _METHOD(_CLASS(this), "menuItemClickCallback", "(I)V"); + (*env)->CallVoidMethod(env, this, onMenuItemClick, id); + if ((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); +} + +static const GActionEntry action_entry = { "clicked", popupmenu_activated, "i", NULL, NULL }; + +JNIEXPORT jlong JNICALL Java_android_widget_PopupMenu_native_1buildPopover(JNIEnv *env, jobject this, jlong menu_ptr) +{ + GtkWidget *popover = gtk_popover_menu_new_from_model(G_MENU_MODEL(_PTR(menu_ptr))); + GSimpleActionGroup *group = g_simple_action_group_new(); + g_action_map_add_action_entries(G_ACTION_MAP(group), &action_entry, 1, _REF(this)); + gtk_widget_insert_action_group(popover, "popupmenu", G_ACTION_GROUP(group)); + return _INTPTR(popover); +} + +JNIEXPORT void JNICALL Java_android_widget_PopupMenu_native_1show(JNIEnv *env, jobject this, jlong popover_ptr, jlong anchor_ptr) +{ + GtkWidget *anchor = gtk_widget_get_parent(GTK_WIDGET(_PTR(anchor_ptr))); + GtkPopover *popover = GTK_POPOVER(_PTR(popover_ptr)); + gtk_widget_insert_before(GTK_WIDGET(popover), GTK_WIDGET(anchor), NULL); + gtk_popover_present(popover); + gtk_popover_popup(popover); +} diff --git a/src/api-impl/android/widget/PopupMenu.java b/src/api-impl/android/widget/PopupMenu.java index 999da674..4b25c68c 100644 --- a/src/api-impl/android/widget/PopupMenu.java +++ b/src/api-impl/android/widget/PopupMenu.java @@ -17,12 +17,19 @@ package android.widget; import android.content.Context; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.SubMenu; import android.view.View; import android.view.View.OnTouchListener; + +import org.xmlpull.v1.XmlPullParser; + import com.android.internal.R; /** @@ -39,6 +46,8 @@ public class PopupMenu { private OnMenuItemClickListener mMenuItemClickListener; private OnDismissListener mOnDismissListener; private OnTouchListener mDragListener; + private long popover; + /** * Constructor to create a new popup menu with an anchor view. * @@ -115,14 +124,136 @@ public class PopupMenu { mOnDismissListener = listener; } + protected native long native_init(); + protected native void native_appendItem(long menu, String item, int id); + protected native long native_buildPopover(long menu); + protected native void native_show(long popover, long anchor); + + // callback from native code + protected void menuItemClickCallback(final int id) { + if (mMenuItemClickListener != null) { + mMenuItemClickListener.onMenuItemClick(new MenuItem() { + + @Override + public MenuItem setIcon(int iconRes) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setIcon'"); + } + + @Override + public MenuItem setVisible(boolean visible) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setVisible'"); + } + + @Override + public MenuItem setChecked(boolean checked) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setChecked'"); + } + + @Override + public MenuItem setEnabled(boolean enabled) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setEnabled'"); + } + + @Override + public MenuItem setCheckable(boolean checkable) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setCheckable'"); + } + + @Override + public MenuItem setTitleCondensed(CharSequence titleCondensed) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setTitleCondensed'"); + } + + @Override + public MenuItem setTitle(CharSequence title) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setTitle'"); + } + + @Override + public MenuItem setActionView(View actionView) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setActionView'"); + } + + @Override + public void setShowAsAction(int action) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setShowAsAction'"); + } + + @Override + public int getItemId() { + return id; + } + + @Override + public int getGroupId() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getGroupId'"); + } + + @Override + public MenuItem setOnMenuItemClickListener(OnMenuItemClickListener listener) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setOnMenuItemClickListener'"); + } + + @Override + public MenuItem setTitle(int resId) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'setTitle'"); + } + + @Override + public boolean isVisible() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isVisible'"); + } + + @Override + public Drawable getIcon() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getIcon'"); + } + + @Override + public SubMenu getSubMenu() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getSubMenu'"); + } + }); + } + } + /** * Inflate a menu resource into this PopupMenu. This is equivalent to * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}. * * @param menuRes Menu resource to inflate + * @throws Exception */ - public void inflate(int menuRes) { - System.out.println("PopupMenu.inflate(" + menuRes + ") called"); + public void inflate(int menuRes) throws Exception { + XmlResourceParser parser = mContext.getResources().getXml(menuRes); + int type; + while ((type=parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT); + if (type == XmlPullParser.START_TAG && "menu".equals(parser.getName())) { + long menu = native_init(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.START_TAG && "item".equals(parser.getName())) { + final TypedArray a = mContext.getResources().obtainAttributes(parser, R.styleable.MenuItem); + native_appendItem(menu, a.getString(R.styleable.MenuItem_title), a.getResourceId(R.styleable.MenuItem_id, -1)); + a.recycle(); + } + } + popover = native_buildPopover(menu); + } } /** @@ -132,6 +263,7 @@ public class PopupMenu { */ public void show() { System.out.println("PopupMenu.show() called"); + native_show(popover, mAnchor.widget); } /**