2022-10-02 23:06:56 +02:00
# include <gtk/gtk.h>
2023-07-14 17:53:12 +02:00
# include "../defines.h"
# include "../util.h"
2022-10-02 23:06:56 +02:00
2023-10-17 21:33:59 +02:00
# include "../generated_headers/android_view_View.h"
2023-08-28 20:03:32 +02:00
# include "WrapperWidget.h"
2024-05-06 06:35:47 +02:00
# include "src/api-impl-jni/views/AndroidLayout.h"
2022-10-02 23:06:56 +02:00
G_DEFINE_TYPE ( WrapperWidget , wrapper_widget , GTK_TYPE_WIDGET )
2024-11-17 15:59:53 +01:00
typedef enum { ATL_ID = 1 , ATL_ID_NAME , ATL_CLASS_NAME , ATL_SUPER_CLASS_NAMES , N_PROPERTIES } WrapperWidgetProperty ;
static GParamSpec * wrapper_widget_properties [ N_PROPERTIES ] = { NULL , } ;
static void wrapper_widget_set_property ( GObject * object , guint property_id , const GValue * value , GParamSpec * pspec )
{
switch ( ( WrapperWidgetProperty ) property_id )
{
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID ( object , property_id , pspec ) ;
break ;
}
}
static void wrapper_widget_get_property ( GObject * object , guint property_id , GValue * value , GParamSpec * pspec )
{
WrapperWidget * self = WRAPPER_WIDGET ( object ) ;
JNIEnv * env = get_jni_env ( ) ;
jobject jobj = self - > jobj ;
jclass class = _CLASS ( jobj ) ;
switch ( ( WrapperWidgetProperty ) property_id )
{
2025-02-05 16:27:17 +01:00
case ATL_ID :
2024-11-17 15:59:53 +01:00
{
jint id_jint = ( * env ) - > CallIntMethod ( env , jobj , handle_cache . view . getId ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
const char * id = g_markup_printf_escaped ( " 0x%08x " , id_jint ) ;
g_value_set_string ( value , id ) ;
break ;
}
case ATL_ID_NAME :
{
jstring id_name_jstring = ( * env ) - > CallObjectMethod ( env , jobj , handle_cache . view . getIdName ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
const char * id_name = ( * env ) - > GetStringUTFChars ( env , id_name_jstring , NULL ) ;
g_value_set_string ( value , id_name ) ;
( * env ) - > ReleaseStringUTFChars ( env , id_name_jstring , id_name ) ;
break ;
}
case ATL_CLASS_NAME :
{
jstring class_name_jstring = ( * env ) - > CallObjectMethod ( env , class , _METHOD ( _CLASS ( class ) , " getName " , " ()Ljava/lang/String; " ) ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
const char * class_name = ( * env ) - > GetStringUTFChars ( env , class_name_jstring , NULL ) ;
g_value_set_string ( value , class_name ) ;
( * env ) - > ReleaseStringUTFChars ( env , class_name_jstring , class_name ) ;
break ;
}
case ATL_SUPER_CLASS_NAMES :
{
jstring super_classes_names_obj = ( * env ) - > CallObjectMethod ( env , jobj , handle_cache . view . getAllSuperClasses ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
const char * super_classes_names = ( * env ) - > GetStringUTFChars ( env , super_classes_names_obj , NULL ) ;
g_value_set_string ( value , super_classes_names ) ;
( * env ) - > ReleaseStringUTFChars ( env , super_classes_names_obj , super_classes_names ) ;
break ;
}
2025-02-05 16:27:17 +01:00
2024-11-17 15:59:53 +01:00
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID ( object , property_id , pspec ) ;
break ;
}
}
2022-11-02 14:37:34 +01:00
static void wrapper_widget_init ( WrapperWidget * wrapper_widget )
2022-10-02 23:06:56 +02:00
{
}
static void wrapper_widget_dispose ( GObject * wrapper_widget )
{
2024-07-26 21:47:08 +02:00
GtkWidget * widget = GTK_WIDGET ( wrapper_widget ) ;
GtkWidget * child = gtk_widget_get_first_child ( widget ) ;
while ( child ) {
GtkWidget * _child = gtk_widget_get_next_sibling ( child ) ;
gtk_widget_unparent ( child ) ;
child = _child ;
}
2023-07-14 17:53:12 +02:00
WrapperWidget * wrapper = WRAPPER_WIDGET ( wrapper_widget ) ;
if ( wrapper - > jvm ) {
JNIEnv * env ;
( * wrapper - > jvm ) - > GetEnv ( wrapper - > jvm , ( void * * ) & env , JNI_VERSION_1_6 ) ;
if ( wrapper - > jobj )
2024-07-26 21:47:08 +02:00
_WEAK_UNREF ( wrapper - > jobj ) ;
2023-07-14 17:53:12 +02:00
if ( wrapper - > canvas )
_UNREF ( wrapper - > canvas ) ;
}
2022-10-02 23:06:56 +02:00
G_OBJECT_CLASS ( wrapper_widget_parent_class ) - > dispose ( wrapper_widget ) ;
}
2023-11-04 07:49:38 +01:00
GtkSizeRequestMode wrapper_widget_get_request_mode ( GtkWidget * widget )
2023-08-28 20:03:32 +02:00
{
2023-10-30 22:37:48 +01:00
WrapperWidget * wrapper = WRAPPER_WIDGET ( widget ) ;
2023-11-04 07:49:38 +01:00
return gtk_widget_get_request_mode ( wrapper - > child ) ;
}
void wrapper_widget_measure ( GtkWidget * widget , GtkOrientation orientation , int for_size , int * minimum , int * natural , int * minimum_baseline , int * natural_baseline )
{
WrapperWidget * wrapper = WRAPPER_WIDGET ( widget ) ;
gtk_widget_measure ( wrapper - > child , orientation , for_size , minimum , natural , minimum_baseline , natural_baseline ) ;
2023-11-10 23:36:57 +01:00
if ( orientation = = GTK_ORIENTATION_HORIZONTAL & & ( wrapper - > layout_width > 0 ) ) {
* minimum = * natural = wrapper - > layout_width ;
} else if ( orientation = = GTK_ORIENTATION_VERTICAL & & ( wrapper - > layout_height > 0 ) ) {
* minimum = * natural = wrapper - > layout_height ;
}
2023-11-04 07:49:38 +01:00
}
void wrapper_widget_allocate ( GtkWidget * widget , int width , int height , int baseline )
{
WrapperWidget * wrapper = WRAPPER_WIDGET ( widget ) ;
2024-05-06 06:35:47 +02:00
if ( ! width & & ! height ) {
width = wrapper - > real_width ;
height = wrapper - > real_height ;
}
2023-11-04 07:49:38 +01:00
GtkAllocation allocation = {
. x = 0 ,
. y = 0 ,
. width = width ,
. height = height ,
} ;
2023-10-30 22:37:48 +01:00
if ( wrapper - > computeScroll_method ) {
2023-11-04 07:49:38 +01:00
// The child needs to know its size before calling computeScroll, so we allocate it twice.
// second allocate will not trigger onLayout, because of unchanged size
gtk_widget_size_allocate ( wrapper - > child , & allocation , baseline ) ;
2023-10-30 22:37:48 +01:00
JNIEnv * env ;
( * wrapper - > jvm ) - > GetEnv ( wrapper - > jvm , ( void * * ) & env , JNI_VERSION_1_6 ) ;
( * env ) - > CallVoidMethod ( env , wrapper - > jobj , wrapper - > computeScroll_method ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
2023-11-04 07:49:38 +01:00
allocation . x = - ( * env ) - > CallIntMethod ( env , wrapper - > jobj , handle_cache . view . getScrollX ) ;
allocation . y = - ( * env ) - > CallIntMethod ( env , wrapper - > jobj , handle_cache . view . getScrollY ) ;
2023-10-30 22:37:48 +01:00
}
2023-11-04 07:49:38 +01:00
2024-05-06 06:35:47 +02:00
if ( ATL_IS_ANDROID_LAYOUT ( gtk_widget_get_layout_manager ( wrapper - > child ) ) ) {
AndroidLayout * layout = ATL_ANDROID_LAYOUT ( gtk_widget_get_layout_manager ( wrapper - > child ) ) ;
if ( layout - > real_width ! = width | | layout - > real_height ! = height ) {
layout - > real_width = width ;
layout - > real_height = height ;
if ( ! layout - > needs_allocation )
gtk_widget_queue_allocate ( wrapper - > child ) ;
}
if ( layout - > needs_allocation )
gtk_widget_size_allocate ( wrapper - > child , & allocation , baseline ) ;
else
gtk_widget_size_allocate ( wrapper - > child , & ( GtkAllocation ) { . x = allocation . x , . y = allocation . y } , baseline ) ;
} else {
gtk_widget_size_allocate ( wrapper - > child , & allocation , baseline ) ;
}
2023-12-29 16:55:11 +01:00
if ( wrapper - > background )
gtk_widget_size_allocate ( wrapper - > background , & allocation , baseline ) ;
2023-10-30 22:37:48 +01:00
}
2023-08-28 20:03:32 +02:00
2024-05-25 19:58:55 +02:00
static void wrapper_widget_snapshot ( GtkWidget * widget , GdkSnapshot * snapshot )
{
WrapperWidget * wrapper = WRAPPER_WIDGET ( widget ) ;
if ( wrapper - > real_height > 0 & & wrapper - > real_width > 0 ) {
gtk_snapshot_push_clip ( snapshot , & GRAPHENE_RECT_INIT ( 0 , 0 , wrapper - > real_width , wrapper - > real_height ) ) ;
}
2024-05-25 19:25:33 +02:00
if ( wrapper - > draw_method ) {
JNIEnv * env = get_jni_env ( ) ;
_SET_LONG_FIELD ( wrapper - > canvas , " snapshot " , _INTPTR ( snapshot ) ) ;
( * env ) - > CallVoidMethod ( env , wrapper - > jobj , wrapper - > draw_method , wrapper - > canvas ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
2024-05-25 19:58:55 +02:00
} else {
2024-05-26 15:53:24 +02:00
GtkWidget * widget = & wrapper - > parent_instance ;
GtkWidget * child = gtk_widget_get_first_child ( widget ) ;
while ( child ) {
gtk_widget_snapshot_child ( widget , child , snapshot ) ;
child = gtk_widget_get_next_sibling ( child ) ;
}
2024-05-25 19:25:33 +02:00
}
2024-05-25 10:38:49 +02:00
if ( wrapper - > real_height > 0 & & wrapper - > real_width > 0 ) {
gtk_snapshot_pop ( snapshot ) ;
}
}
2022-10-02 23:06:56 +02:00
static void wrapper_widget_class_init ( WrapperWidgetClass * class )
{
2024-06-22 14:35:56 +02:00
GObjectClass * object_class = G_OBJECT_CLASS ( class ) ;
GtkWidgetClass * widget_class = GTK_WIDGET_CLASS ( class ) ;
2022-10-02 23:06:56 +02:00
object_class - > dispose = wrapper_widget_dispose ;
2023-11-04 07:49:38 +01:00
widget_class - > get_request_mode = wrapper_widget_get_request_mode ;
widget_class - > measure = wrapper_widget_measure ;
widget_class - > size_allocate = wrapper_widget_allocate ;
2024-05-25 10:38:49 +02:00
widget_class - > snapshot = wrapper_widget_snapshot ;
2024-11-17 15:59:53 +01:00
object_class - > set_property = wrapper_widget_set_property ;
object_class - > get_property = wrapper_widget_get_property ;
2025-02-05 16:27:17 +01:00
2024-11-17 15:59:53 +01:00
// According to testing, these properties are not evaluated till we open the GtkInspector
wrapper_widget_properties [ ATL_ID ] = g_param_spec_string ( " ATL-id " , " ATL: ID " , " ID of the component " , " " , G_PARAM_READABLE ) ;
wrapper_widget_properties [ ATL_ID_NAME ] = g_param_spec_string ( " ATL-id-name " , " ATL: ID name " , " Name of the ID of the component " , " " , G_PARAM_READABLE ) ;
wrapper_widget_properties [ ATL_CLASS_NAME ] = g_param_spec_string ( " ATL-class-name " , " ATL: Class name " , " Name of the class of the component " , " " , G_PARAM_READABLE ) ;
wrapper_widget_properties [ ATL_SUPER_CLASS_NAMES ] = g_param_spec_string ( " ATL-superclasses-names " , " ATL: Super classes names " , " Names of all the superclasses of the component class " , " " , G_PARAM_READABLE ) ;
2025-02-05 16:27:17 +01:00
2024-11-17 15:59:53 +01:00
g_object_class_install_properties ( object_class , N_PROPERTIES , wrapper_widget_properties ) ;
2022-10-02 23:06:56 +02:00
}
GtkWidget * wrapper_widget_new ( void )
{
return g_object_new ( wrapper_widget_get_type ( ) , NULL ) ;
}
void wrapper_widget_set_child ( WrapperWidget * parent , GtkWidget * child ) // TODO: make sure there can only be one child
{
gtk_widget_insert_before ( child , GTK_WIDGET ( parent ) , NULL ) ;
2023-10-31 23:14:47 +01:00
parent - > child = child ;
2022-10-02 23:06:56 +02:00
}
2023-07-14 17:53:12 +02:00
2024-05-25 19:25:33 +02:00
static guint queue_queue_redraw ( GtkWidget * widget )
2023-08-28 20:03:32 +02:00
{
2024-05-25 19:25:33 +02:00
gtk_widget_queue_draw ( widget ) ;
2023-10-31 23:14:47 +01:00
return G_SOURCE_REMOVE ;
}
void wrapper_widget_queue_draw ( WrapperWidget * wrapper )
{
2024-05-25 19:25:33 +02:00
if ( wrapper - > draw_method ) {
/* schedule the call to gtk_widget_queue_draw for a future event loop pass in case we're currently inside the snapshot */
2023-10-31 23:14:47 +01:00
/* GTK+ uses G_PRIORITY_HIGH_IDLE + 10 for resizing operations, and G_PRIORITY_HIGH_IDLE + 20 for redrawing operations. */
2024-05-25 19:25:33 +02:00
g_idle_add_full ( G_PRIORITY_HIGH_IDLE + 20 , G_SOURCE_FUNC ( queue_queue_redraw ) , & wrapper - > parent_instance , NULL ) ;
2023-10-31 23:14:47 +01:00
}
if ( wrapper - > child )
gtk_widget_queue_draw ( wrapper - > child ) ;
2023-11-04 07:49:38 +01:00
if ( wrapper - > computeScroll_method )
gtk_widget_queue_allocate ( GTK_WIDGET ( wrapper ) ) ;
2023-08-28 20:03:32 +02:00
}
2024-02-09 10:05:22 +01:00
static bool on_click ( GtkGestureClick * gesture , int n_press , double x , double y , jobject this )
{
JNIEnv * env = get_jni_env ( ) ;
bool ret = ( * env ) - > CallBooleanMethod ( env , this , handle_cache . view . performClick ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
return ret ;
}
2024-11-30 17:46:43 +01:00
# define KEYCODE_DPAD_UP 19
# define KEYCODE_DPAD_DOWN 20
# define KEYCODE_DPAD_LEFT 21
# define KEYCODE_DPAD_RIGHT 22
# define KEYCODE_ENTER 66
# define KEYCODE_DEL 67
# define KEYCODE_FORWARD_DEL 112
static int map_key_code ( int key_code ) {
switch ( key_code ) {
case GDK_KEY_Up :
return KEYCODE_DPAD_UP ;
case GDK_KEY_Down :
return KEYCODE_DPAD_DOWN ;
case GDK_KEY_Left :
return KEYCODE_DPAD_LEFT ;
case GDK_KEY_Right :
return KEYCODE_DPAD_RIGHT ;
case GDK_KEY_Return :
return KEYCODE_ENTER ;
case GDK_KEY_BackSpace :
return KEYCODE_DEL ;
case GDK_KEY_Delete :
return KEYCODE_FORWARD_DEL ;
default :
return key_code ;
}
}
# define ACTION_DOWN 0
# define ACTION_UP 1
static gboolean on_key_pressed ( GtkEventControllerKey * controller , guint keyval , guint keycode , GdkModifierType state , WrapperWidget * wrapper )
{
JNIEnv * env = get_jni_env ( ) ;
jobject key_event = ( * env ) - > NewObject ( env , handle_cache . key_event . class , handle_cache . key_event . constructor , ACTION_DOWN , map_key_code ( keyval ) ) ;
_SET_INT_FIELD ( key_event , " unicodeValue " , gdk_keyval_to_unicode ( keyval ) ) ;
gboolean ret = ( * env ) - > CallBooleanMethod ( env , wrapper - > jobj , handle_cache . view . dispatchKeyEvent , key_event ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
return ret ;
}
static gboolean on_key_released ( GtkEventControllerKey * controller , guint keyval , guint keycode , GdkModifierType state , WrapperWidget * wrapper )
{
JNIEnv * env = get_jni_env ( ) ;
jobject key_event = ( * env ) - > NewObject ( env , handle_cache . key_event . class , handle_cache . key_event . constructor , ACTION_UP , map_key_code ( keyval ) ) ;
_SET_INT_FIELD ( key_event , " unicodeValue " , gdk_keyval_to_unicode ( keyval ) ) ;
gboolean ret = ( * env ) - > CallBooleanMethod ( env , wrapper - > jobj , handle_cache . view . dispatchKeyEvent , key_event ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
return ret ;
}
2023-07-14 17:53:12 +02:00
void wrapper_widget_set_jobject ( WrapperWidget * wrapper , JNIEnv * env , jobject jobj )
{
JavaVM * jvm ;
( * env ) - > GetJavaVM ( env , & jvm ) ;
wrapper - > jvm = jvm ;
2024-07-26 21:47:08 +02:00
wrapper - > jobj = _WEAK_REF ( jobj ) ;
2024-05-19 15:04:15 +02:00
jmethodID on_draw_method = _METHOD ( _CLASS ( jobj ) , " onDraw " , " (Landroid/graphics/Canvas;)V " ) ;
2024-11-27 14:59:37 +01:00
jmethodID dispatch_draw_method = _METHOD ( _CLASS ( jobj ) , " dispatchDraw " , " (Landroid/graphics/Canvas;)V " ) ;
2024-05-19 15:04:15 +02:00
jmethodID draw_method = _METHOD ( _CLASS ( jobj ) , " draw " , " (Landroid/graphics/Canvas;)V " ) ;
2024-11-27 14:59:37 +01:00
if ( on_draw_method ! = handle_cache . view . onDraw | | draw_method ! = handle_cache . view . draw | | dispatch_draw_method ! = handle_cache . view . dispatchDraw ) {
2023-07-14 17:53:12 +02:00
wrapper - > draw_method = draw_method ;
2024-05-25 19:25:33 +02:00
jclass canvas_class = ( * env ) - > FindClass ( env , " android/graphics/GskCanvas " ) ;
jmethodID canvas_constructor = _METHOD ( canvas_class , " <init> " , " (J)V " ) ;
wrapper - > canvas = _REF ( ( * env ) - > NewObject ( env , canvas_class , canvas_constructor , 0 ) ) ;
( * env ) - > DeleteLocalRef ( env , canvas_class ) ;
2023-08-28 20:03:32 +02:00
}
2023-10-17 21:33:59 +02:00
jmethodID ontouchevent_method = _METHOD ( _CLASS ( jobj ) , " onTouchEvent " , " (Landroid/view/MotionEvent;)Z " ) ;
2024-11-27 14:59:37 +01:00
jmethodID dispatchtouchevent_method = _METHOD ( _CLASS ( jobj ) , " dispatchTouchEvent " , " (Landroid/view/MotionEvent;)Z " ) ;
2024-12-10 23:41:33 +01:00
wrapper - > custom_dispatch_touch = dispatchtouchevent_method ! = handle_cache . view . dispatchTouchEvent ;
if ( ontouchevent_method ! = handle_cache . view . onTouchEvent | | wrapper - > custom_dispatch_touch ) {
2024-07-26 21:47:08 +02:00
_setOnTouchListener ( env , jobj , GTK_WIDGET ( wrapper ) ) ;
2023-10-17 21:33:59 +02:00
}
2023-10-30 22:37:48 +01:00
jmethodID computeScroll_method = _METHOD ( _CLASS ( jobj ) , " computeScroll " , " ()V " ) ;
if ( computeScroll_method ! = handle_cache . view . computeScroll ) {
wrapper - > computeScroll_method = computeScroll_method ;
}
2024-02-09 10:05:22 +01:00
jmethodID performClick_method = _METHOD ( _CLASS ( jobj ) , " performClick " , " ()Z " ) ;
if ( performClick_method ! = handle_cache . view . performClick ) {
GtkEventController * controller = GTK_EVENT_CONTROLLER ( gtk_gesture_click_new ( ) ) ;
2024-07-26 21:47:08 +02:00
g_signal_connect ( controller , " released " , G_CALLBACK ( on_click ) , wrapper - > jobj ) ;
2024-02-09 10:05:22 +01:00
gtk_widget_add_controller ( wrapper - > child , controller ) ;
2024-05-06 06:35:47 +02:00
widget_set_needs_allocation ( wrapper - > child ) ;
2024-02-09 10:05:22 +01:00
}
2024-11-30 17:46:43 +01:00
jmethodID dispatch_key_event_method = _METHOD ( _CLASS ( jobj ) , " dispatchKeyEvent " , " (Landroid/view/KeyEvent;)Z " ) ;
if ( dispatch_key_event_method ! = handle_cache . view . dispatchKeyEvent ) {
GtkEventController * controller = GTK_EVENT_CONTROLLER ( gtk_event_controller_key_new ( ) ) ;
g_signal_connect ( controller , " key-pressed " , G_CALLBACK ( on_key_pressed ) , wrapper ) ;
g_signal_connect ( controller , " key-released " , G_CALLBACK ( on_key_released ) , wrapper ) ;
gtk_widget_add_controller ( GTK_WIDGET ( wrapper ) , controller ) ;
gtk_widget_set_focusable ( GTK_WIDGET ( wrapper ) , TRUE ) ;
}
2023-07-14 17:53:12 +02:00
}
2023-11-10 23:36:57 +01:00
void wrapper_widget_set_layout_params ( WrapperWidget * wrapper , int width , int height )
{
wrapper - > layout_width = width ;
wrapper - > layout_height = height ;
}
2023-12-29 16:55:11 +01:00
void wrapper_widget_set_background ( WrapperWidget * wrapper , GdkPaintable * paintable )
{
if ( ! wrapper - > background ) {
wrapper - > background = gtk_picture_new ( ) ;
gtk_widget_insert_after ( wrapper - > background , GTK_WIDGET ( wrapper ) , NULL ) ;
}
gtk_picture_set_paintable ( GTK_PICTURE ( wrapper - > background ) , paintable ) ;
}
2024-07-19 14:06:52 +02:00
static gboolean on_touch_event_consume ( GtkEventControllerLegacy * controller , GdkEvent * event ) {
switch ( gdk_event_get_event_type ( event ) ) {
case GDK_BUTTON_PRESS :
case GDK_TOUCH_BEGIN :
case GDK_BUTTON_RELEASE :
case GDK_TOUCH_END :
case GDK_MOTION_NOTIFY :
case GDK_TOUCH_UPDATE :
return TRUE ;
default :
return FALSE ;
}
}
// Add default on touch listener, which just consumes all events to prevent bubbling to the parent
void wrapper_widget_consume_touch_events ( WrapperWidget * wrapper )
{
GtkEventController * controller = GTK_EVENT_CONTROLLER ( gtk_event_controller_legacy_new ( ) ) ;
g_signal_connect ( controller , " event " , G_CALLBACK ( on_touch_event_consume ) , NULL ) ;
gtk_widget_add_controller ( GTK_WIDGET ( wrapper ) , controller ) ;
g_object_set_data ( G_OBJECT ( wrapper ) , " on_touch_listener " , controller ) ;
}