From 6547e66d4f930a6bd7ff9b9a4ee0722000573244 Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Thu, 10 Aug 2023 11:06:02 +0200 Subject: [PATCH] implement android.app.AlertDialog using GtkDialog --- meson.build | 1 + .../app/android_app_AlertDialog.c | 115 ++++++++++++++++++ .../android_app_AlertDialog.h | 61 ++++++++++ src/api-impl/android/app/AlertDialog.java | 80 +++++++++++- .../android/content/DialogInterface.java | 10 ++ 5 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 src/api-impl-jni/app/android_app_AlertDialog.c create mode 100644 src/api-impl-jni/generated_headers/android_app_AlertDialog.h diff --git a/meson.build b/meson.build index 78c21548..87b3b429 100644 --- a/meson.build +++ b/meson.build @@ -84,6 +84,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/android_opengl_GLES20.c', 'src/api-impl-jni/location/android_location_LocationManager.c', 'src/api-impl-jni/app/android_app_Activity.c', + 'src/api-impl-jni/app/android_app_AlertDialog.c', ] + marshal_files, install: true, install_dir : get_option('libdir') / 'java/dex/android_translation_layer/natives', diff --git a/src/api-impl-jni/app/android_app_AlertDialog.c b/src/api-impl-jni/app/android_app_AlertDialog.c new file mode 100644 index 00000000..6e70ff35 --- /dev/null +++ b/src/api-impl-jni/app/android_app_AlertDialog.c @@ -0,0 +1,115 @@ +#include +#include + +#include "../defines.h" +#include "../generated_headers/android_app_AlertDialog.h" + +JNIEXPORT jlong JNICALL Java_android_app_AlertDialog_nativeInit(JNIEnv *env, jobject this) +{ + GtkWidget *dialog = gtk_dialog_new(); + g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_window_destroy), dialog); + return _INTPTR(dialog); +} + +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetTitle(JNIEnv *env, jobject this, jlong ptr, jstring title) +{ + GtkWindow *dialog = GTK_WINDOW(_PTR(ptr)); + const char* nativeTitle = (*env)->GetStringUTFChars(env, title, NULL); + gtk_window_set_title(dialog, nativeTitle); + (*env)->ReleaseStringUTFChars(env, title, nativeTitle); +} + +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetMessage(JNIEnv *env, jobject this, jlong ptr, jstring message) +{ + GtkDialog *dialog = GTK_DIALOG(_PTR(ptr)); + const char* nativeMessage = (*env)->GetStringUTFChars(env, message, NULL); + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget *label = gtk_label_new(nativeMessage); + gtk_label_set_wrap(GTK_LABEL(label), TRUE); + gtk_box_append(GTK_BOX(content_area), label); + (*env)->ReleaseStringUTFChars(env, message, nativeMessage); +} + +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetButton(JNIEnv *env, jobject this, jlong ptr, jint id, jstring text) { + GtkDialog *dialog = GTK_DIALOG(_PTR(ptr)); + const char* nativeText = (*env)->GetStringUTFChars(env, text, NULL); + gtk_dialog_add_button(dialog, nativeText, id); + (*env)->ReleaseStringUTFChars(env, text, nativeText); +} + +struct click_callback_data { + JavaVM *jvm; + jobject this; + jobject on_click_listener; + jmethodID on_click_method; +}; + +struct _ListEntry { + GObject parent; + const char *text; +}; +G_DECLARE_FINAL_TYPE(ListEntry, list_entry, ATL, LIST_ENTRY, GObject); +static void list_entry_class_init(ListEntryClass *cls){} +static void list_entry_init(ListEntry *self){} +G_DEFINE_TYPE(ListEntry, list_entry, G_TYPE_OBJECT) + +static void setup_listitem_cb(GtkListItemFactory *factory, GtkListItem *list_item) +{ + gtk_list_item_set_child(list_item, gtk_label_new("")); +} + +static void bind_listitem_cb(GtkListItemFactory *factory, GtkListItem *list_item) +{ + GtkWidget *label = gtk_list_item_get_child(list_item); + ListEntry *entry = gtk_list_item_get_item(list_item); + + gtk_label_set_text(GTK_LABEL(label), entry->text); +} + +static void activate_cb(GtkListView *list, guint position, struct click_callback_data *d) +{ + JNIEnv *env; + (*d->jvm)->GetEnv(d->jvm, (void**)&env, JNI_VERSION_1_6); + + (*env)->CallVoidMethod(env, d->on_click_listener, d->on_click_method, d->this, position); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); +} + +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetItems(JNIEnv *env, jobject this, jlong ptr, jobjectArray items, jobject on_click) { + GtkDialog *dialog = GTK_DIALOG(_PTR(ptr)); + + GListStore *store = g_list_store_new(list_entry_get_type()); + int stringCount = (*env)->GetArrayLength(env, items); + for (int i=0; itext = _CSTRING((*env)->GetObjectArrayElement(env, items, i)); + g_list_store_append(store, entry); + } + + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); + g_signal_connect(factory, "setup", G_CALLBACK(setup_listitem_cb), NULL); + g_signal_connect(factory, "bind", G_CALLBACK(bind_listitem_cb), NULL); + GtkWidget *list = gtk_list_view_new(GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(store))), factory); + gtk_list_view_set_single_click_activate(GTK_LIST_VIEW(list), TRUE); + + GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + gtk_box_append(GTK_BOX(content_area), list); + + JavaVM *jvm; + (*env)->GetJavaVM(env, &jvm); + + struct click_callback_data *callback_data = malloc(sizeof(struct click_callback_data)); + callback_data->jvm = jvm; + callback_data->this = _REF(this); + callback_data->on_click_listener = _REF(on_click); + callback_data->on_click_method = _METHOD(_CLASS(on_click), "onClick", "(Landroid/content/DialogInterface;I)V"); + + g_signal_connect(list, "activate", G_CALLBACK(activate_cb), callback_data); +} + +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeShow(JNIEnv *env, jobject this, jlong ptr) +{ + GtkWindow *dialog = GTK_WINDOW(_PTR(ptr)); + gtk_window_present(dialog); +} diff --git a/src/api-impl-jni/generated_headers/android_app_AlertDialog.h b/src/api-impl-jni/generated_headers/android_app_AlertDialog.h new file mode 100644 index 00000000..b6ee7be1 --- /dev/null +++ b/src/api-impl-jni/generated_headers/android_app_AlertDialog.h @@ -0,0 +1,61 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class android_app_AlertDialog */ + +#ifndef _Included_android_app_AlertDialog +#define _Included_android_app_AlertDialog +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: android_app_AlertDialog + * Method: nativeInit + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_android_app_AlertDialog_nativeInit + (JNIEnv *, jobject); + +/* + * Class: android_app_AlertDialog + * Method: nativeSetTitle + * Signature: (JLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetTitle + (JNIEnv *, jobject, jlong, jstring); + +/* + * Class: android_app_AlertDialog + * Method: nativeSetMessage + * Signature: (JLjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetMessage + (JNIEnv *, jobject, jlong, jstring); + +/* + * Class: android_app_AlertDialog + * Method: nativeSetButton + * Signature: (JILjava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetButton + (JNIEnv *, jobject, jlong, jint, jstring); + +/* + * Class: android_app_AlertDialog + * Method: nativeSetItems + * Signature: (J[Ljava/lang/String;Landroid/content/DialogInterface/OnClickListener;)V + */ +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeSetItems + (JNIEnv *, jobject, jlong, jobjectArray, jobject); + +/* + * Class: android_app_AlertDialog + * Method: nativeShow + * Signature: (J)V + */ +JNIEXPORT void JNICALL Java_android_app_AlertDialog_nativeShow + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/api-impl/android/app/AlertDialog.java b/src/api-impl/android/app/AlertDialog.java index e4b01d32..95d8d790 100644 --- a/src/api-impl/android/app/AlertDialog.java +++ b/src/api-impl/android/app/AlertDialog.java @@ -2,21 +2,74 @@ package android.app; import android.content.Context; import android.content.DialogInterface; +import android.os.Handler; +import android.os.Looper; import android.view.View; -public class AlertDialog extends Dialog { +public class AlertDialog extends Dialog implements DialogInterface { + + private long nativePtr; + + private native long nativeInit(); + private native void nativeSetTitle(long ptr, String title); + private native void nativeSetMessage(long ptr, String message); + private native void nativeSetButton(long ptr, int whichButton, String text); + private native void nativeSetItems(long ptr, String[] items, DialogInterface.OnClickListener listener); + private native void nativeShow(long ptr); + + public AlertDialog(Context context) { + nativePtr = nativeInit(); + } + + public void setTitle(CharSequence title) { + nativeSetTitle(nativePtr, String.valueOf(title)); + } + + public void setOnCancelListener(OnCancelListener onCancelListener) {} + + public void setCancelable(boolean cancelable) {} + + public void setMessage(CharSequence message) { + System.out.println("AlertDialog setMessage called with: '" + message + "'"); + nativeSetMessage(nativePtr, String.valueOf(message)); + } + + public void setButton(int whichButton, CharSequence text, OnClickListener listener) { + nativeSetButton(nativePtr, whichButton, String.valueOf(text)); + } + + public boolean isShowing() { + return false; + } + + @Override + public void show() { + super.show(); + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + nativeShow(nativePtr); + } + }); + } + public static class Builder { + private Context context; + private AlertDialog dialog; + public Builder(Context context) { System.out.println("making an AlertDialog$Builder as we speak, my word!"); + this.context = context; + dialog = new AlertDialog(context); } public AlertDialog.Builder setPositiveButton(int textId, DialogInterface.OnClickListener listener) { - System.out.println("AlertDialog.Builder setPositiveButton called with textId: '" + textId + "'"); - return this; + return setPositiveButton(context.getResources().getText(textId), listener); } public AlertDialog.Builder setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) { System.out.println("AlertDialog.Builder setPositiveButton called with text: '" + text + "'"); + dialog.setButton(DialogInterface.BUTTON_POSITIVE, text, listener); return this; } @@ -30,11 +83,17 @@ public class AlertDialog extends Dialog { public AlertDialog.Builder setTitle(CharSequence title) { System.out.println("AlertDialog.Builder setTitle called with: '" + title + "'"); + dialog.setTitle(title); return this; } + public AlertDialog.Builder setTitle(int title) { + return setTitle(context.getResources().getText(title)); + } + public AlertDialog.Builder setMessage(CharSequence message) { System.out.println("AlertDialog.Builder setMessage called with: '" + message + "'"); + dialog.setMessage(message); return this; } @@ -42,8 +101,21 @@ public class AlertDialog extends Dialog { return this; } + public AlertDialog.Builder setItems(CharSequence[] items, final DialogInterface.OnClickListener listener) { + String[] stringItems = new String[items.length]; + for (int i = 0; i < items.length; i++) { + stringItems[i] = String.valueOf(items[i]); + } + dialog.nativeSetItems(dialog.nativePtr, stringItems, listener); + return this; + } + + public Builder setOnCancelListener(OnCancelListener onCancelListener) { + return this; + } + public AlertDialog create() { - return new AlertDialog(); + return dialog; } } } diff --git a/src/api-impl/android/content/DialogInterface.java b/src/api-impl/android/content/DialogInterface.java index 53ff4238..4cda3250 100644 --- a/src/api-impl/android/content/DialogInterface.java +++ b/src/api-impl/android/content/DialogInterface.java @@ -1,10 +1,20 @@ package android.content; public interface DialogInterface { + /** The identifier for the positive button. */ + int BUTTON_POSITIVE = -1; + /** The identifier for the negative button. */ + int BUTTON_NEGATIVE = -2; + /** The identifier for the neutral button. */ + int BUTTON_NEUTRAL = -3; + public interface OnDismissListener { } public interface OnClickListener { + void onClick(DialogInterface dialog, int which); } public interface OnShowListener { } + public interface OnCancelListener { + } }