diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c index 4f41025f6c9..1b1a6f1f684 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4.c @@ -806,6 +806,20 @@ JNIEXPORT void JNICALL GTK4_NATIVE(gsk_1render_1node_1unref) } #endif +#ifndef NO_gtk_1actionable_1set_1action_1name +JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1actionable_1set_1action_1name) + (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1) +{ + jbyte *lparg1=NULL; + GTK4_NATIVE_ENTER(env, that, gtk_1actionable_1set_1action_1name_FUNC); + if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail; + gtk_actionable_set_action_name((GtkActionable *)arg0, (const char *)lparg1); +fail: + if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0); + GTK4_NATIVE_EXIT(env, that, gtk_1actionable_1set_1action_1name_FUNC); +} +#endif + #ifndef NO_gtk_1box_1append JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1box_1append) (JNIEnv *env, jclass that, jlong arg0, jlong arg1) @@ -2270,6 +2284,22 @@ JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1picture_1set_1paintable) } #endif +#ifndef NO_gtk_1popover_1menu_1add_1child +JNIEXPORT jboolean JNICALL GTK4_NATIVE(gtk_1popover_1menu_1add_1child) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jbyteArray arg2) +{ + jbyte *lparg2=NULL; + jboolean rc = 0; + GTK4_NATIVE_ENTER(env, that, gtk_1popover_1menu_1add_1child_FUNC); + if (arg2) if ((lparg2 = (*env)->GetByteArrayElements(env, arg2, NULL)) == NULL) goto fail; + rc = (jboolean)gtk_popover_menu_add_child((GtkPopoverMenu *)arg0, (GtkWidget *)arg1, (const char *)lparg2); +fail: + if (arg2 && lparg2) (*env)->ReleaseByteArrayElements(env, arg2, lparg2, 0); + GTK4_NATIVE_EXIT(env, that, gtk_1popover_1menu_1add_1child_FUNC); + return rc; +} +#endif + #ifndef NO_gtk_1popover_1menu_1bar_1new_1from_1model JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1popover_1menu_1bar_1new_1from_1model) (JNIEnv *env, jclass that, jlong arg0) @@ -2306,6 +2336,18 @@ JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1popover_1menu_1new_1from_1model_1full) } #endif +#ifndef NO_gtk_1popover_1menu_1remove_1child +JNIEXPORT jboolean JNICALL GTK4_NATIVE(gtk_1popover_1menu_1remove_1child) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1) +{ + jboolean rc = 0; + GTK4_NATIVE_ENTER(env, that, gtk_1popover_1menu_1remove_1child_FUNC); + rc = (jboolean)gtk_popover_menu_remove_child((GtkPopoverMenu *)arg0, (GtkWidget *)arg1); + GTK4_NATIVE_EXIT(env, that, gtk_1popover_1menu_1remove_1child_FUNC); + return rc; +} +#endif + #ifndef NO_gtk_1popover_1menu_1set_1menu_1model JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1popover_1menu_1set_1menu_1model) (JNIEnv *env, jclass that, jlong arg0, jlong arg1) @@ -2488,6 +2530,28 @@ JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1shortcut_1new) } #endif +#ifndef NO_gtk_1size_1group_1add_1widget +JNIEXPORT void JNICALL GTK4_NATIVE(gtk_1size_1group_1add_1widget) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1) +{ + GTK4_NATIVE_ENTER(env, that, gtk_1size_1group_1add_1widget_FUNC); + gtk_size_group_add_widget((GtkSizeGroup *)arg0, (GtkWidget *)arg1); + GTK4_NATIVE_EXIT(env, that, gtk_1size_1group_1add_1widget_FUNC); +} +#endif + +#ifndef NO_gtk_1size_1group_1new +JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1size_1group_1new) + (JNIEnv *env, jclass that, jint arg0) +{ + jlong rc = 0; + GTK4_NATIVE_ENTER(env, that, gtk_1size_1group_1new_FUNC); + rc = (jlong)gtk_size_group_new((GtkSizeGroupMode)arg0); + GTK4_NATIVE_EXIT(env, that, gtk_1size_1group_1new_FUNC); + return rc; +} +#endif + #ifndef NO_gtk_1snapshot_1append_1cairo JNIEXPORT jlong JNICALL GTK4_NATIVE(gtk_1snapshot_1append_1cairo) (JNIEnv *env, jclass that, jlong arg0, jlong arg1) diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h index f240d03cae9..3d221478b10 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/gtk4_stats.h @@ -84,6 +84,7 @@ typedef enum { gdk_1toplevel_1size_1set_1size_FUNC, gsk_1render_1node_1draw_FUNC, gsk_1render_1node_1unref_FUNC, + gtk_1actionable_1set_1action_1name_FUNC, gtk_1box_1append_FUNC, gtk_1box_1insert_1child_1after_FUNC, gtk_1box_1prepend_FUNC, @@ -186,9 +187,11 @@ typedef enum { gtk_1picture_1new_FUNC, gtk_1picture_1set_1can_1shrink_FUNC, gtk_1picture_1set_1paintable_FUNC, + gtk_1popover_1menu_1add_1child_FUNC, gtk_1popover_1menu_1bar_1new_1from_1model_FUNC, gtk_1popover_1menu_1get_1menu_1model_FUNC, gtk_1popover_1menu_1new_1from_1model_1full_FUNC, + gtk_1popover_1menu_1remove_1child_FUNC, gtk_1popover_1menu_1set_1menu_1model_FUNC, gtk_1popover_1new_FUNC, gtk_1popover_1set_1autohide_FUNC, @@ -205,6 +208,8 @@ typedef enum { gtk_1shortcut_1controller_1remove_1shortcut_FUNC, gtk_1shortcut_1controller_1set_1scope_FUNC, gtk_1shortcut_1new_FUNC, + gtk_1size_1group_1add_1widget_FUNC, + gtk_1size_1group_1new_FUNC, gtk_1snapshot_1append_1cairo_FUNC, gtk_1snapshot_1free_1to_1node_FUNC, gtk_1snapshot_1new_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c index 24178bd35c9..d4627b94e33 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c @@ -3774,6 +3774,18 @@ JNIEXPORT void JNICALL GTK_NATIVE(gtk_1box_1set_1spacing) } #endif +#ifndef NO_gtk_1button_1get_1type +JNIEXPORT jlong JNICALL GTK_NATIVE(gtk_1button_1get_1type) + (JNIEnv *env, jclass that) +{ + jlong rc = 0; + GTK_NATIVE_ENTER(env, that, gtk_1button_1get_1type_FUNC); + rc = (jlong)gtk_button_get_type(); + GTK_NATIVE_EXIT(env, that, gtk_1button_1get_1type_FUNC); + return rc; +} +#endif + #ifndef NO_gtk_1button_1new JNIEXPORT jlong JNICALL GTK_NATIVE(gtk_1button_1new) (JNIEnv *env, jclass that) @@ -7068,6 +7080,24 @@ JNIEXPORT void JNICALL GTK_NATIVE(gtk_1style_1context_1add_1provider) } #endif +#ifndef NO_gtk_1style_1context_1add_1provider_1for_1display +JNIEXPORT void JNICALL GTK_NATIVE(gtk_1style_1context_1add_1provider_1for_1display) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1, jint arg2) +{ + GTK_NATIVE_ENTER(env, that, gtk_1style_1context_1add_1provider_1for_1display_FUNC); +/* + gtk_style_context_add_provider_for_display((GdkDisplay *)arg0, (GtkStyleProvider *)arg1, (guint)arg2); +*/ + { + GTK_LOAD_FUNCTION(fp, gtk_style_context_add_provider_for_display) + if (fp) { + ((void (CALLING_CONVENTION*)(GdkDisplay *, GtkStyleProvider *, guint))fp)((GdkDisplay *)arg0, (GtkStyleProvider *)arg1, (guint)arg2); + } + } + GTK_NATIVE_EXIT(env, that, gtk_1style_1context_1add_1provider_1for_1display_FUNC); +} +#endif + #ifndef NO_gtk_1style_1context_1remove_1class JNIEXPORT void JNICALL GTK_NATIVE(gtk_1style_1context_1remove_1class) (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1) @@ -9424,6 +9454,28 @@ JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1realize) } #endif +#ifndef NO_gtk_1widget_1remove_1css_1class +JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1remove_1css_1class) + (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1) +{ + jbyte *lparg1=NULL; + GTK_NATIVE_ENTER(env, that, gtk_1widget_1remove_1css_1class_FUNC); + if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail; +/* + gtk_widget_remove_css_class((GtkWidget *)arg0, (const char *)lparg1); +*/ + { + GTK_LOAD_FUNCTION(fp, gtk_widget_remove_css_class) + if (fp) { + ((void (CALLING_CONVENTION*)(GtkWidget *, const char *))fp)((GtkWidget *)arg0, (const char *)lparg1); + } + } +fail: + if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0); + GTK_NATIVE_EXIT(env, that, gtk_1widget_1remove_1css_1class_FUNC); +} +#endif + #ifndef NO_gtk_1widget_1set_1can_1focus JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1set_1can_1focus) (JNIEnv *env, jclass that, jlong arg0, jboolean arg1) @@ -9630,6 +9682,16 @@ JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1set_1size_1request) } #endif +#ifndef NO_gtk_1widget_1set_1state_1flags +JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1set_1state_1flags) + (JNIEnv *env, jclass that, jlong arg0, jint arg1, jboolean arg2) +{ + GTK_NATIVE_ENTER(env, that, gtk_1widget_1set_1state_1flags_FUNC); + gtk_widget_set_state_flags((GtkWidget *)arg0, (GtkStateFlags)arg1, arg2); + GTK_NATIVE_EXIT(env, that, gtk_1widget_1set_1state_1flags_FUNC); +} +#endif + #ifndef NO_gtk_1widget_1set_1tooltip_1text JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1set_1tooltip_1text) (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1) @@ -9684,6 +9746,16 @@ JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1unparent) } #endif +#ifndef NO_gtk_1widget_1unset_1state_1flags +JNIEXPORT void JNICALL GTK_NATIVE(gtk_1widget_1unset_1state_1flags) + (JNIEnv *env, jclass that, jlong arg0, jint arg1) +{ + GTK_NATIVE_ENTER(env, that, gtk_1widget_1unset_1state_1flags_FUNC); + gtk_widget_unset_state_flags((GtkWidget *)arg0, (GtkStateFlags)arg1); + GTK_NATIVE_EXIT(env, that, gtk_1widget_1unset_1state_1flags_FUNC); +} +#endif + #ifndef NO_gtk_1window_1fullscreen JNIEXPORT void JNICALL GTK_NATIVE(gtk_1window_1fullscreen) (JNIEnv *env, jclass that, jlong arg0) @@ -11984,6 +12056,20 @@ JNIEXPORT void JNICALL OS_NATIVE(g_1menu_1item_1set_1attribute) } #endif +#ifndef NO_g_1menu_1item_1set_1attribute_1value +JNIEXPORT void JNICALL OS_NATIVE(g_1menu_1item_1set_1attribute_1value) + (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1, jlong arg2) +{ + jbyte *lparg1=NULL; + OS_NATIVE_ENTER(env, that, g_1menu_1item_1set_1attribute_1value_FUNC); + if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail; + g_menu_item_set_attribute_value((GMenuItem *)arg0, (const gchar *)lparg1, (GVariant *)arg2); +fail: + if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, 0); + OS_NATIVE_EXIT(env, that, g_1menu_1item_1set_1attribute_1value_FUNC); +} +#endif + #ifndef NO_g_1menu_1item_1set_1label JNIEXPORT void JNICALL OS_NATIVE(g_1menu_1item_1set_1label) (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1) @@ -12076,14 +12162,33 @@ JNIEXPORT jlong JNICALL OS_NATIVE(g_1object_1get_1qdata) } #endif -#ifndef NO_g_1object_1new -JNIEXPORT jlong JNICALL OS_NATIVE(g_1object_1new) +#ifndef NO_g_1object_1new__JJ +JNIEXPORT jlong JNICALL OS_NATIVE(g_1object_1new__JJ) (JNIEnv *env, jclass that, jlong arg0, jlong arg1) { jlong rc = 0; - OS_NATIVE_ENTER(env, that, g_1object_1new_FUNC); + OS_NATIVE_ENTER(env, that, g_1object_1new__JJ_FUNC); rc = (jlong)g_object_new((GType)arg0, (const gchar *)arg1); - OS_NATIVE_EXIT(env, that, g_1object_1new_FUNC); + OS_NATIVE_EXIT(env, that, g_1object_1new__JJ_FUNC); + return rc; +} +#endif + +#ifndef NO_g_1object_1new__J_3B_3BJ +JNIEXPORT jlong JNICALL OS_NATIVE(g_1object_1new__J_3B_3BJ) + (JNIEnv *env, jclass that, jlong arg0, jbyteArray arg1, jbyteArray arg2, jlong arg3) +{ + jbyte *lparg1=NULL; + jbyte *lparg2=NULL; + jlong rc = 0; + OS_NATIVE_ENTER(env, that, g_1object_1new__J_3B_3BJ_FUNC); + if (arg1) if ((lparg1 = (*env)->GetByteArrayElements(env, arg1, NULL)) == NULL) goto fail; + if (arg2) if ((lparg2 = (*env)->GetByteArrayElements(env, arg2, NULL)) == NULL) goto fail; + rc = (jlong)g_object_new((GType)arg0, (const gchar *)lparg1, (const gchar *)lparg2, (const gchar *)NULL); +fail: + if (arg2 && lparg2) (*env)->ReleaseByteArrayElements(env, arg2, lparg2, JNI_ABORT); + if (arg1 && lparg1) (*env)->ReleaseByteArrayElements(env, arg1, lparg1, JNI_ABORT); + OS_NATIVE_EXIT(env, that, g_1object_1new__J_3B_3BJ_FUNC); return rc; } #endif diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h index f30ca89d0ad..51c1e64be29 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os_stats.h @@ -296,6 +296,7 @@ typedef enum { gtk_1box_1new_FUNC, gtk_1box_1set_1homogeneous_FUNC, gtk_1box_1set_1spacing_FUNC, + gtk_1button_1get_1type_FUNC, gtk_1button_1new_FUNC, gtk_1button_1set_1label_FUNC, gtk_1button_1set_1use_1underline_FUNC, @@ -567,6 +568,7 @@ typedef enum { gtk_1spin_1button_1update_FUNC, gtk_1style_1context_1add_1class_FUNC, gtk_1style_1context_1add_1provider_FUNC, + gtk_1style_1context_1add_1provider_1for_1display_FUNC, gtk_1style_1context_1remove_1class_FUNC, gtk_1style_1context_1restore_FUNC, gtk_1style_1context_1save_FUNC, @@ -751,6 +753,7 @@ typedef enum { gtk_1widget_1queue_1draw_FUNC, gtk_1widget_1queue_1resize_FUNC, gtk_1widget_1realize_FUNC, + gtk_1widget_1remove_1css_1class_FUNC, gtk_1widget_1set_1can_1focus_FUNC, gtk_1widget_1set_1default_1direction_FUNC, gtk_1widget_1set_1direction_FUNC, @@ -766,11 +769,13 @@ typedef enum { gtk_1widget_1set_1receives_1default_FUNC, gtk_1widget_1set_1sensitive_FUNC, gtk_1widget_1set_1size_1request_FUNC, + gtk_1widget_1set_1state_1flags_FUNC, gtk_1widget_1set_1tooltip_1text_FUNC, gtk_1widget_1set_1valign_FUNC, gtk_1widget_1set_1vexpand_FUNC, gtk_1widget_1set_1visible_FUNC, gtk_1widget_1unparent_FUNC, + gtk_1widget_1unset_1state_1flags_FUNC, gtk_1window_1fullscreen_FUNC, gtk_1window_1get_1default_1size_FUNC, gtk_1window_1get_1default_1widget_FUNC, @@ -969,6 +974,7 @@ typedef enum { g_1menu_1item_1new_1section_FUNC, g_1menu_1item_1new_1submenu_FUNC, g_1menu_1item_1set_1attribute_FUNC, + g_1menu_1item_1set_1attribute_1value_FUNC, g_1menu_1item_1set_1label_FUNC, g_1menu_1item_1set_1submenu_FUNC, g_1menu_1new_FUNC, @@ -976,7 +982,8 @@ typedef enum { g_1object_1get__J_3B_3IJ_FUNC, g_1object_1get__J_3B_3JJ_FUNC, g_1object_1get_1qdata_FUNC, - g_1object_1new_FUNC, + g_1object_1new__JJ_FUNC, + g_1object_1new__J_3B_3BJ_FUNC, g_1object_1notify_FUNC, g_1object_1ref_FUNC, g_1object_1ref_1sink_FUNC, diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java index 9a6c2cb9cf8..9f9cce70c94 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/GTK.java @@ -258,6 +258,7 @@ public class GTK extends OS { /* GtkButton */ + public static final native long gtk_button_get_type(); public static final native long gtk_button_new(); /** * @method flags=dynamic @@ -1038,6 +1039,13 @@ public class GTK extends OS { * @param priority cast=(guint) */ public static final native void gtk_style_context_add_provider(long context, long provider, int priority); + /** + * @method flags=dynamic + * @param display cast=(GdkDisplay *) + * @param provider cast=(GtkStyleProvider *) + * @param priority cast=(guint) + */ + public static final native void gtk_style_context_add_provider_for_display(long display, long provider, int priority); /** * @param context cast=(GtkStyleContext *) * @param class_name cast=(const gchar *) @@ -2270,6 +2278,16 @@ public class GTK extends OS { public static final native void gtk_widget_set_margin_bottom(long widget, int margin); /** @param self cast=(GtkWidget *) */ public static final native int gtk_widget_get_state_flags(long self); + /** + * @param widget cast=(GtkWidget *) + * @param flags cast=(GtkStateFlags) + */ + public static final native void gtk_widget_unset_state_flags(long widget, int flags); + /** + * @param widget cast=(GtkWidget *) + * @param flags cast=(GtkStateFlags) + */ + public static final native void gtk_widget_set_state_flags(long widget, int flags, boolean clear); /** @param widget cast=(GtkWidget *) */ public static final native boolean gtk_widget_has_default(long widget); @@ -2281,6 +2299,12 @@ public class GTK extends OS { * @param css_class cast=(const char *) * */ public static final native void gtk_widget_add_css_class(long widget, byte[] css_class); + /** + * @method flags=dynamic + * @param widget cast=(GtkWidget *) + * @param css_class cast=(const char *) + * */ + public static final native void gtk_widget_remove_css_class(long widget, byte[] css_class); /** @param widget cast=(GtkWidget *) */ public static final native boolean gtk_widget_child_focus(long widget, int direction); /** diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java index a070c4d1329..c9a9359d3ac 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk/OS.java @@ -1277,6 +1277,13 @@ public static boolean isX11 () { * @param first_property_name cast=(const gchar *) */ public static final native long g_object_new(long type, long first_property_name); +/** + * @param type cast=(GType) + * @param first_property_name cast=(const gchar *),flags=no_out + * @param value cast=(const gchar *),flags=no_out + * @param terminator cast=(const gchar *),flags=sentinel + */ +public static final native long g_object_new(long type, byte[] first_property_name, byte[] value, long terminator); /** * @param object cast=(GObject *) * @param property_name cast=(const gchar *) @@ -2417,6 +2424,12 @@ public static final native long g_dbus_proxy_new_for_bus_sync(int bus_type, int * @param data cast=(const gchar *) */ public static final native void g_menu_item_set_attribute(long menu_item, byte[] attribute, byte[] format_string, long data); +/** + * @param menu_item cast=(GMenuItem *) + * @param attribute cast=(const gchar *) + * @param value cast=(GVariant *) + */ +public static final native void g_menu_item_set_attribute_value(long menu_item, byte[] attribute, long value); /* GSimpleActionGroup */ public static final native long g_simple_action_group_new(); diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java index 3c33dfcfb79..45fa77c7549 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/gtk/org/eclipse/swt/internal/gtk4/GTK4.java @@ -21,6 +21,7 @@ public class GTK4 { public static final int GTK_POPOVER_MENU_NESTED = 1 << 0; + public static final int GTK_SIZE_GROUP_HORIZONTAL = 1; /* * GdkDragAction values. Note that GTK4 redefined these compared to GTK3 @@ -75,6 +76,13 @@ public class GTK4 { */ public static final native void gtk_rgb_to_hsv(float r, float g, float b, float[] h, float[] s, float[] v); + /* GtkActionable */ + /** + * @param actionable cast=(GtkActionable *) + * @param action_name cast=(const char *) + */ + public static final native void gtk_actionable_set_action_name(long actionable, byte[] action_name); + /* GtkBox */ /** * @param box cast=(GtkBox *) @@ -533,6 +541,15 @@ public class GTK4 { /** @param window cast=(GtkWindow *) */ public static final native void gtk_window_present(long window) ; + /* GtkSizeGroup */ + /** @param mode cast=(GtkSizeGroupMode) */ + public static final native long gtk_size_group_new(int mode); + /** + * @param size_group cast=(GtkSizeGroup *) + * @param widget cast=(GtkWidget *) + */ + public static final native void gtk_size_group_add_widget(long size_group, long widget); + /* GtkShortcutController */ public static final native long gtk_shortcut_controller_new(); /** @@ -590,6 +607,17 @@ public class GTK4 { public static final native void gtk_text_set_tabs(long entry, long tabs); /* GtkPopoverMenu */ + /** + * @param popover cast=(GtkPopoverMenu *) + * @param child cast=(GtkWidget *) + * @param id cast=(const char *) + */ + public static final native boolean gtk_popover_menu_add_child(long popover, long child, byte[] id); + /** + * @param popover cast=(GtkPopoverMenu *) + * @param child cast=(GtkWidget *) + */ + public static final native boolean gtk_popover_menu_remove_child(long popover, long child); /** * @param model cast=(GMenuModel *) * @param flags cast=(GtkPopoverMenuFlags) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java index 4a1bc89cce3..a4e74520f0f 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java @@ -136,6 +136,16 @@ public class Display extends Device implements Executor { long snapshotDrawProc, keyPressReleaseProc, focusProc, windowActiveProc, enterMotionProc, leaveProc, scrollProc, resizeProc, layoutProc, activateProc, gesturePressReleaseProc; long menuItemsChangedProc; + /** GTK4 only: when the last key event was dispatched, see Menu#clearStrayRowSelection. */ + long lastKeyEventTime; + /** + * GTK4 only: set while SWT is mutating a GMenu. The removal's synchronous + * "items-changed" re-enters menu wiring, so position-based custom widget + * injection must wait until the model is whole again (see MenuItem#refreshMenuModelGTK4). + */ + boolean menuModelMutating; + /** GTK4 only: the MenuItem custom-icon-row selection CSS has been installed, see ensureMenuIconRowCss. */ + private boolean menuIconRowCssApplied; long notifyProc; long computeSizeProc; Callback windowCallback2, windowCallback3, windowCallback4, windowCallback5, windowCallback6; @@ -862,6 +872,24 @@ void addSkinnableWidget (Widget widget) { skinList [skinCount++] = widget; } +/** + * GTK4 only: installs, once per display, the rule that paints the selection + * highlight of MenuItem custom icon rows (see MenuItem.setCustomRowSelected). + * Installed above the user priority so it wins over the per-widget background a + * themed workbench (e.g. the Eclipse e4 dark theme) attaches to these SWT-owned + * rows. Painting with the row's text color at a low alpha matches native rows in + * any theme without a hard-coded color. + */ +void ensureMenuIconRowCss() { + if (menuIconRowCssApplied) return; + menuIconRowCssApplied = true; + long provider = GTK.gtk_css_provider_new(); + GTK4.gtk_css_provider_load_from_data(provider, + Converter.wcsToMbcs("modelbutton." + MenuItem.CUSTOM_SELECTED_CLASS + " { background-color: alpha(currentColor, 0.1); }", true), -1); + GTK.gtk_style_context_add_provider_for_display(GDK.gdk_display_get_default(), provider, GTK.GTK_STYLE_PROVIDER_PRIORITY_USER + 1); + OS.g_object_unref(provider); +} + void addWidget (long handle, Widget widget) { if (handle == 0) return; // Last element in the indexTable is -1, so if freeSlot == -1 we have no place anymore @@ -6123,6 +6151,7 @@ boolean scrollProc(long controller, double dx, double dy, long user_data) { } boolean keyPressReleaseProc(long controller, int keyval, int keycode, int state, long user_data) { + lastKeyEventTime = System.currentTimeMillis(); long handle = GTK.gtk_event_controller_get_widget(controller); Widget widget = getWidget(handle); if (widget == null) return false; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java index 19467e4b2d3..e76a3b3c9cd 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Menu.java @@ -56,6 +56,8 @@ public class Menu extends Widget { /** GTK4 only fields */ long modelHandle, popoverHandle, actionGroup, shortcutController; + /** GTK4 only: aligns the label column of custom and native rows, see alignRowLabels(). */ + long sizeGroupHandle; class Section { LinkedList sectionItems; @@ -576,6 +578,7 @@ void createHandle (int index) { default: handle = GTK4.gtk_popover_menu_new_from_model_full(modelHandle, GTK4.GTK_POPOVER_MENU_NESTED); GTK.gtk_widget_set_parent(handle, parent.handle); + hookPopoverFocusEnter(handle); GTK.gtk_popover_set_position(handle, GTK.GTK_POS_BOTTOM); GTK4.gtk_popover_set_has_arrow(handle, false); GTK.gtk_widget_set_halign(handle, GTK.GTK_ALIGN_START); @@ -937,7 +940,9 @@ long gtk_map (long widget) { * The POP_UP GtkPopoverMenu has been mapped. Its handle IS the GtkPopoverMenu, * and nested GtkPopoverMenu children for any CASCADE submenus already exist in * its widget tree. Connect SHOW/HIDE signals for those nested submenus now. + * Also inject any custom icon widgets into the popover. */ + injectCustomMenuIcons(); connectCascadeSubMenuSignals(this, handle); } } @@ -1020,28 +1025,29 @@ private void wireSubMenuPopover(Menu submenu, long popover) { OS.g_object_ref(popover); submenu.popoverHandle = popover; display.addWidget(popover, submenu); + submenu.hookPopoverFocusEnter(popover); OS.g_signal_connect_closure_by_id(popover, display.signalIds[SHOW], 0, display.getClosure(SHOW), false); OS.g_signal_connect_closure_by_id(popover, display.signalIds[HIDE], 0, display.getClosure(HIDE), false); } private void connectDropDownMenuSignals() { if (items == null) return; - long barItem = GTK4.gtk_widget_get_first_child(handle); - for (MenuItem menuItem : items) { - if (barItem == 0) break; - if ((menuItem.style & SWT.SEPARATOR) != 0) continue; - if (menuItem.menu != null) { - long popover = findGtkPopoverMenuChild(barItem); - /* Re-wire when the discovered popover differs from the cache (initial or GTK rebuilt it). */ - if (popover != 0 && menuItem.menu.popoverHandle != popover) { - wireSubMenuPopover(menuItem.menu, popover); - } - if (menuItem.menu.popoverHandle != 0) { - connectCascadeSubMenuSignals(menuItem.menu); - } + if (menuItem.menu == null) continue; + /* + * Locate the popover by its menu model, not positionally: this also runs + * from "items-changed" mid create/dispose, when the item list and the + * bar's children are out of step and a positional walk wires the wrong menu. + */ + long popover = findNestedPopoverForModel(handle, menuItem.menu.modelHandle); + /* Re-wire when the discovered popover differs from the cache (initial or GTK rebuilt it). */ + if (popover != 0 && menuItem.menu.popoverHandle != popover) { + wireSubMenuPopover(menuItem.menu, popover); + } + if (menuItem.menu.popoverHandle != 0) { + menuItem.menu.injectCustomMenuIcons(); + connectCascadeSubMenuSignals(menuItem.menu); } - barItem = GTK4.gtk_widget_get_next_sibling(barItem); } } @@ -1065,12 +1071,220 @@ private void connectCascadeSubMenuSignals(Menu menu, long parentPopoverHandle) { if (item.menu.popoverHandle != nestedPopover) { wireSubMenuPopover(item.menu, nestedPopover); } + item.menu.injectCustomMenuIcons(); connectCascadeSubMenuSignals(item.menu); } } } } +/** + * GTK4: hides any showing CASCADE submenu of this menu, mirroring GtkModelButton + * closing an open sibling submenu as the pointer moves on. Hidden via + * gtk_widget_set_visible(FALSE) like GTK does, not gtk_popover_popdown(), which + * would cascade to the parent popover and close the whole menu. + */ +void hideOpenSubmenus() { + // Walk the live widget tree rather than trusting each submenu's cached + // popoverHandle, which GTK may have rebuilt on a model change (a stale handle + // then hits GTK_IS_WIDGET). + long popover = (style & SWT.POP_UP) != 0 ? handle : popoverHandle; + if (popover != 0) hideVisibleSubmenus(popover, popover); +} + +private void hideVisibleSubmenus(long widget, long root) { + for (long child = GTK4.gtk_widget_get_first_child(widget); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { + if (GTK4.GTK_IS_POPOVER_MENU(child)) { + if (child != root && GTK.gtk_widget_get_visible(child)) GTK.gtk_widget_set_visible(child, false); + } else { + hideVisibleSubmenus(child, root); + } + } +} + +/** + * GTK4: hooks focus-enter and motion controllers onto every menu popover. Hiding + * a focused row (a submenu closing, a row being rebuilt) makes GtkWindow move + * focus onto some other row of the popup, selecting it although the pointer is + * elsewhere; the focus controller sweeps that stray selection (see + * clearStrayRowSelection). The motion controller keeps the pointer's row + * highlighted grab-independently (see syncRowSelectionRecursive). + */ +void hookPopoverFocusEnter(long popover) { + long focusController = GTK4.gtk_event_controller_focus_new(); + OS.g_signal_connect(focusController, OS.enter, display.focusProc, FOCUS_IN); + GTK4.gtk_widget_add_controller(popover, focusController); + long motionController = GTK4.gtk_event_controller_motion_new(); + OS.g_signal_connect(motionController, OS.enter, display.enterMotionProc, ENTER); + OS.g_signal_connect(motionController, OS.motion, display.enterMotionProc, MOTION); + GTK4.gtk_widget_add_controller(popover, motionController); +} + +@Override +void gtk4_enter_event(long controller, double x, double y, long event) { + gtk4_motion_event(controller, x, y, event); +} + +@Override +void gtk4_motion_event(long controller, double x, double y, long event) { + if (System.currentTimeMillis() - display.lastKeyEventTime < 500) return; + // The motion may be delivered to any popover of the menu (the grab holder), so + // sync from the root popover down, into open submenus. + long popover = GTK.gtk_event_controller_get_widget(controller); + for (long p = GTK.gtk_widget_get_parent(popover); p != 0; p = GTK.gtk_widget_get_parent(p)) { + if (GTK4.GTK_IS_POPOVER_MENU(p)) popover = p; + } + syncRowSelectionRecursive(popover); +} + +/* + * GtkPopoverMenu stops updating a row's selection once a submenu holds the pointer + * grab, so re-derive every row's selection from its "prelight" state, which + * crossing events set on the row under the pointer regardless of the grab. + */ +private void syncRowSelectionRecursive(long widget) { + // Skip a closed submenu's whole subtree: it has no hoverable rows, and a large + // menu keeps all submenus instantiated, so walking them would slow every motion. + if (GTK4.GTK_IS_POPOVER_MENU(widget) && !GTK.gtk_widget_get_visible(widget)) return; + Widget item = display.getWidget(widget); + boolean custom = item instanceof MenuItem menuItem && menuItem.customWidgetHandle == widget; + if (custom || isModelButton(widget)) { + int flags = GTK.gtk_widget_get_state_flags(widget); + boolean selected = (flags & GTK.GTK_STATE_FLAG_PRELIGHT) != 0 || hasVisibleSubmenu(widget); + if (custom) { + /* Toggles the highlight class and forces the repaint, see MenuItem. */ + ((MenuItem) item).setCustomRowSelected(selected); + } else if (selected && (flags & GTK.GTK_STATE_FLAG_SELECTED) == 0) { + GTK.gtk_widget_set_state_flags(widget, GTK.GTK_STATE_FLAG_SELECTED, false); + } else if (!selected && (flags & GTK.GTK_STATE_FLAG_SELECTED) != 0) { + GTK.gtk_widget_unset_state_flags(widget, GTK.GTK_STATE_FLAG_SELECTED); + } + /* A cascade row hosts its submenu popover as a child; keep descending. */ + } + for (long child = GTK4.gtk_widget_get_first_child(widget); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { + syncRowSelectionRecursive(child); + } +} + +@Override +void gtk4_focus_enter_event(long controller, long event) { + long popover = GTK.gtk_event_controller_get_widget(controller); + display.asyncExec(() -> { + if (!isDisposed()) clearStrayRowSelection(popover); + }); +} + +/** GTK4: clears a "selected" highlight left on a row that is neither hovered nor showing a submenu. */ +void clearStrayRowSelection(long popover) { + if (popover == 0) return; + /* Keyboard navigation selects rows through focus as well; leave those alone. */ + if (System.currentTimeMillis() - display.lastKeyEventTime < 500) return; + for (long child = GTK4.gtk_widget_get_first_child(popover); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { + clearStrayRowSelectionRecursive(child); + } +} + +private void clearStrayRowSelectionRecursive(long widget) { + /* Nested submenu popovers are children of their cascade row; leave them alone. */ + if (GTK4.GTK_IS_POPOVER_MENU(widget)) return; + int flags = GTK.gtk_widget_get_state_flags(widget); + Widget item = display.getWidget(widget); + boolean custom = item instanceof MenuItem menuItem && menuItem.customWidgetHandle == widget; + if ((custom || isModelButton(widget)) && (flags & GTK.GTK_STATE_FLAG_SELECTED) != 0 + && (flags & GTK.GTK_STATE_FLAG_PRELIGHT) == 0 && !hasVisibleSubmenu(widget)) { + if (custom) { + ((MenuItem) item).setCustomRowSelected(false); + } else { + GTK.gtk_widget_unset_state_flags(widget, GTK.GTK_STATE_FLAG_SELECTED); + } + } + for (long child = GTK4.gtk_widget_get_first_child(widget); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { + clearStrayRowSelectionRecursive(child); + } +} + +private static boolean hasVisibleSubmenu(long row) { + for (long child = GTK4.gtk_widget_get_first_child(row); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { + if (GTK4.GTK_IS_POPOVER_MENU(child) && GTK.gtk_widget_get_visible(child)) return true; + } + return false; +} + +/** + * (Re-)injects the custom icon+label widgets of this menu's PUSH items into the + * GtkPopoverMenu, for those items that have one. A no-op for items whose widget + * is already attached. + */ +void injectCustomMenuIcons() { + if (items == null || display.menuModelMutating) return; + for (MenuItem item : items) { + if (item.customWidgetHandle != 0) { + item.injectCustomWidgetGTK4(); + } + } + updateCustomRowGutters(); +} + +/** + * GTK4: shows the invisible indicator gutter of custom PUSH rows while the menu + * has a CHECK/RADIO item, so their icons line up with a CHECK row's icon, as GTK3 + * menus reserve toggle space for all items once one has a toggle. + */ +void updateCustomRowGutters() { + if (items == null) return; + boolean hasToggle = false; + for (MenuItem item : items) { + if ((item.style & (SWT.CHECK | SWT.RADIO)) != 0) { + hasToggle = true; + break; + } + } + for (MenuItem item : items) { + if (item.customIndicatorHandle != 0 && (item.style & SWT.CHECK) == 0) { + GTK.gtk_widget_set_visible(item.customIndicatorHandle, hasToggle); + } + } + alignRowLabels(); +} + +/** + * GTK4: keeps custom and native row labels in one column by joining the custom + * rows' leading boxes and the native rows' indicator boxes into one horizontal + * GtkSizeGroup. The group drops destroyed widgets and ignores duplicates, so this + * can simply run again whenever custom rows are (re)injected. + */ +private void alignRowLabels() { + long popover = (style & SWT.POP_UP) != 0 ? handle : popoverHandle; + if (popover == 0) return; + boolean hasCustom = false; + for (MenuItem item : items) { + if (item.customLeadingBoxHandle != 0) hasCustom = true; + } + if (!hasCustom) return; + if (sizeGroupHandle == 0) sizeGroupHandle = GTK4.gtk_size_group_new(GTK4.GTK_SIZE_GROUP_HORIZONTAL); + for (MenuItem item : items) { + if (item.customLeadingBoxHandle != 0) GTK4.gtk_size_group_add_widget(sizeGroupHandle, item.customLeadingBoxHandle); + } + addNativeIndicatorBoxes(popover); +} + +private void addNativeIndicatorBoxes(long widget) { + for (long child = GTK4.gtk_widget_get_first_child(widget); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) { + /* Nested submenu popovers are children of their cascade row and align their own rows. */ + if (GTK4.GTK_IS_POPOVER_MENU(child)) continue; + if (isModelButton(child)) { + long box = GTK4.gtk_widget_get_first_child(child); + if (box != 0 && GTK.GTK_IS_BOX(box)) GTK4.gtk_size_group_add_widget(sizeGroupHandle, box); + } else { + addNativeIndicatorBoxes(child); + } + } +} + +private static boolean isModelButton(long widget) { + return "GtkModelButton".equals(Converter.cCharPtrToJavaString(OS.g_type_name(OS.G_OBJECT_TYPE(widget)), false)); +} + /** * Recursively searches the widget subtree rooted at {@code parentWidget} for the * nested GtkPopoverMenu whose GMenuModel is {@code targetModel}, returning its @@ -1094,17 +1308,6 @@ private long findNestedPopoverForModel(long parentWidget, long targetModel) { } -private long findGtkPopoverMenuChild(long barItem) { - long child = GTK4.gtk_widget_get_first_child(barItem); - while (child != 0) { - if (GTK4.GTK_IS_POPOVER_MENU(child)) { - return child; - } - child = GTK4.gtk_widget_get_next_sibling(child); - } - return 0; -} - @Override long gtk_hide (long widget) { if ((style & SWT.POP_UP) != 0) { @@ -1137,6 +1340,7 @@ long gtk_show (long widget) { sendEvent (SWT.Show); /* Wire cascade submenu SHOW/HIDE signals once the DROP_DOWN popover is shown. */ if (GTK.GTK4 && (style & SWT.DROP_DOWN) != 0 && popoverHandle != 0) { + injectCustomMenuIcons(); connectCascadeSubMenuSignals(this, popoverHandle); } if (OS.ubuntu_menu_proxy_get() != 0) { @@ -1336,6 +1540,10 @@ void releaseWidget () { cascade = null; if (imageList != null) imageList.dispose (); imageList = null; + if (sizeGroupHandle != 0) { + OS.g_object_unref(sizeGroupHandle); + sizeGroupHandle = 0; + } } /** diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MenuItem.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MenuItem.java index b28f41db7e1..e79d6e9b718 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MenuItem.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/MenuItem.java @@ -82,6 +82,30 @@ public class MenuItem extends Item { * API. */ String actionName; + /** GTK4 only: custom widget for icon+text display in GtkPopoverMenu */ + long customWidgetHandle, customImageHandle, customLabelHandle, customAccelHandle; + /** + * GTK4 only: leading indicator slot of the custom row. A live GtkCheckButton for + * CHECK items, an invisible width-reserving one for PUSH items (see updateCustomRowGutters). + */ + long customIndicatorHandle; + /** + * GTK4 only: box holding the indicator slot and icon; shares a GtkSizeGroup with + * the native rows' indicator boxes so all labels start in one column. + */ + long customLeadingBoxHandle; + String customId; + private static int customIdSeq = 0; + /** GTK4 only: CSS class toggled on the custom row to show its selection highlight, see Display.ensureMenuIconRowCss. */ + static final String CUSTOM_SELECTED_CLASS = "swt-selected"; + /** GTK4 only: pointer is over the custom widget, see gtk4_enter_event. */ + private boolean customRowHovered; + /** GTK4 only: custom widget holds the keyboard focus, see gtk4_focus_enter_event. */ + private boolean customRowFocused; + /** GTK4 only: identifies the latest hover timer, older ones are no-ops. */ + private int hoverSerial; + /** Hover time (ms) before a custom row closes a sibling's open submenu. */ + private static final int SUBMENU_CLOSE_DELAY = 100; /** * Constructs a new instance of this class given its parent @@ -355,18 +379,33 @@ void createHandle(int index) { section = parent.new Section(this); int itemsToMove = selectedSection.sectionItems.size() - sectionRelativeIndex; - for (int i = 0; i < itemsToMove; i++) { - MenuItem removedItem = selectedSection.sectionItems.remove(sectionRelativeIndex); - section.sectionItems.add(removedItem); + /* Moving items walks the model out of step; hold off injection until the split completes. */ + boolean wasMutating = display.menuModelMutating; + display.menuModelMutating = true; + try { + for (int i = 0; i < itemsToMove; i++) { + MenuItem removedItem = selectedSection.sectionItems.remove(sectionRelativeIndex); + section.sectionItems.add(removedItem); + + OS.g_menu_remove(selectedSection.getSectionHandle(), sectionRelativeIndex); + /* + * The moved row is rebuilt from scratch; give it a fresh id. A stale one + * can still match the destroyed slot in GtkMenuSectionBox's id table + * ("Duplicate custom ID"), leaving the new row without a slot. + */ + if (removedItem.customWidgetHandle != 0) removedItem.reassignCustomId(); + OS.g_menu_insert_item(modelHandle, section.sectionItems.indexOf(removedItem), removedItem.handle); + removedItem.section = section; + } - OS.g_menu_remove(selectedSection.getSectionHandle(), sectionRelativeIndex); - OS.g_menu_insert_item(modelHandle, section.sectionItems.indexOf(removedItem), removedItem.handle); - removedItem.section = section; + int sectionInsertIndex = parent.sections.indexOf(selectedSection) + 1; + parent.sections.add(sectionInsertIndex, section); + OS.g_menu_insert_item(parent.modelHandle, sectionInsertIndex, handle); + } finally { + display.menuModelMutating = wasMutating; } - - int sectionInsertIndex = parent.sections.indexOf(selectedSection) + 1; - parent.sections.add(sectionInsertIndex, section); - OS.g_menu_insert_item(parent.modelHandle, sectionInsertIndex, handle); + /* The split destroyed the moved rows' custom widgets; re-inject now that the model is whole. */ + parent.injectCustomMenuIcons(); } else { section = selectedSection; selectedSection.sectionItems.add(sectionRelativeIndex, this); @@ -374,6 +413,8 @@ void createHandle(int index) { } parent.items.add(index, this); + /* A first toggle item makes the custom rows show their indicator gutter. */ + if ((style & (SWT.CHECK | SWT.RADIO)) != 0) parent.updateCustomRowGutters(); } else { byte[] buffer = new byte[1]; @@ -783,6 +824,18 @@ void releaseWidget() { * leaks the action for the lifetime of the parent's action group. */ if (parent.actionGroup != 0 && actionId != null) OS.g_action_map_remove_action(parent.actionGroup, Converter.javaStringToCString(actionId)); + if (customWidgetHandle != 0) { + detachCustomMenuWidget(); + display.removeWidget(customWidgetHandle); + OS.g_object_unref(customWidgetHandle); + customWidgetHandle = 0; + customImageHandle = 0; + customLabelHandle = 0; + customAccelHandle = 0; + customIndicatorHandle = 0; + customLeadingBoxHandle = 0; + customId = null; + } } else { long accelGroup = getAccelGroup(); if (accelGroup != 0) removeAccelerator(accelGroup); @@ -800,24 +853,42 @@ void releaseWidget() { @Override void destroyWidget() { if (GTK.GTK4) { - if ((style & SWT.SEPARATOR) != 0) { - Section aboveSection = parent.sections.get(parent.sections.indexOf(section) - 1); - aboveSection.sectionItems.addAll(section.sectionItems); + /* + * Removing this item (or merging its section away) walks the model out of + * step with SWT's bookkeeping; hold off custom widget injection until the + * model is whole again (see refreshMenuModelGTK4). + */ + boolean wasMutating = display.menuModelMutating; + display.menuModelMutating = true; + try { + if ((style & SWT.SEPARATOR) != 0) { + Section aboveSection = parent.sections.get(parent.sections.indexOf(section) - 1); + aboveSection.sectionItems.addAll(section.sectionItems); + + /* Drop the old section before re-inserting its items, so old rows are gone before new ones are built. */ + OS.g_menu_remove(parent.modelHandle, parent.sections.indexOf(section)); + + for (MenuItem item : section.sectionItems) { + item.section = aboveSection; + /* Fresh id for the rebuilt row, see the SEPARATOR split in createHandle. */ + if (item.customWidgetHandle != 0) item.reassignCustomId(); + OS.g_menu_insert_item(aboveSection.getSectionHandle(), aboveSection.sectionItems.indexOf(item), item.handle); + } - for (MenuItem item : section.sectionItems) { - item.section = aboveSection; - OS.g_menu_insert_item(aboveSection.getSectionHandle(), aboveSection.sectionItems.indexOf(item), item.handle); + parent.sections.remove(section); + } else { + OS.g_menu_remove(section.getSectionHandle(), section.sectionItems.indexOf(this)); + section.sectionItems.remove(this); } - - OS.g_menu_remove(parent.modelHandle, parent.sections.indexOf(section)); - - parent.sections.remove(section); - } else { - OS.g_menu_remove(section.getSectionHandle(), section.sectionItems.indexOf(this)); - section.sectionItems.remove(this); + } finally { + display.menuModelMutating = wasMutating; } + /* The merge destroyed the moved rows' custom widgets; re-inject now that the model is whole. */ + parent.injectCustomMenuIcons(); parent.items.remove(this); + /* The last toggle item leaving the menu collapses the custom rows' gutter. */ + parent.updateCustomRowGutters(); parent = null; if (modelHandle != 0) OS.g_object_unref(modelHandle); @@ -1072,9 +1143,6 @@ public void setID (int id) { */ @Override public void setImage (Image image) { - //TODO: GTK4 Menu images with text are no longer supported - if (GTK.GTK4) return; - checkWidget(); if (this.image == image) return; if ((style & SWT.SEPARATOR) != 0) return; @@ -1085,6 +1153,10 @@ public void setImage (Image image) { } private void _setImage (Image image) { + if (GTK.GTK4) { + _setImageGTK4(image); + return; + } if (image != null) { ImageList imageList = parent.imageList; if (imageList == null) imageList = parent.imageList = new ImageList (); @@ -1130,6 +1202,365 @@ private void _setImage (Image image) { } } +private void _setImageGTK4(Image image) { + // Only PUSH and CHECK items use a custom icon+label row; CASCADE and RADIO keep + // their native GtkModelButton. CASCADE never gets a custom slot (its submenu link + // wins), and RADIO needs a detailed action name a lone GtkCheckButton cannot give. + if ((style & (SWT.CASCADE | SWT.RADIO)) != 0) { + return; + } + if (image != null) { + long pixbuf = ImageList.createPixbuf(image); + if (pixbuf != 0) { + long texture = GDK.gdk_texture_new_for_pixbuf(pixbuf); + OS.g_object_unref(pixbuf); + if (texture != 0) { + boolean firstTime = customWidgetHandle == 0; + if (firstTime) { + createCustomMenuWidget(); + } + GTK4.gtk_image_set_from_paintable(customImageHandle, texture); + OS.g_object_unref(texture); + if (firstTime) { + // Inject now in case the menu is already open; otherwise + // Menu.injectCustomMenuIcons() retries when the menu is shown. + injectCustomWidgetGTK4(); + } + } + } + } else { + if (customWidgetHandle != 0) { + destroyCustomMenuWidget(); + refreshMenuModelGTK4(); + } + } +} + +private void createCustomMenuWidget() { + /* + * Use the "modelbutton" CSS node, not "button", so theme and SWT menu-row rules + * give the custom row the same padding, border and height as the native rows. The + * CSS name is construct-only, hence g_object_new instead of gtk_button_new. + */ + customWidgetHandle = OS.g_object_new(GTK.gtk_button_get_type(), + Converter.javaStringToCString("css-name"), Converter.javaStringToCString("modelbutton"), 0); + OS.g_object_ref_sink(customWidgetHandle); + GTK.gtk_widget_add_css_class(customWidgetHandle, Converter.javaStringToCString("flat")); + + display.ensureMenuIconRowCss(); + + long hbox = GTK.gtk_box_new(GTK.GTK_ORIENTATION_HORIZONTAL, 0); + /* Indicator slot and icon share one leading box; Menu.alignRowLabels() size-groups it with the native rows. */ + customLeadingBoxHandle = GTK.gtk_box_new(GTK.GTK_ORIENTATION_HORIZONTAL, 0); + /* + * An inert GtkCheckButton leads the row, excluded from hit testing and focus so + * clicks reach the enclosing button. For CHECK it is the indicator, bound to the + * item's action; for PUSH it is transparent and only reserves the toggle-gutter + * width (shown while the menu has toggle items, see updateCustomRowGutters). + */ + customIndicatorHandle = GTK.gtk_check_button_new(); + OS.g_object_set(customIndicatorHandle, Converter.javaStringToCString("can-target"), false, 0); + GTK4.gtk_widget_set_focusable(customIndicatorHandle, false); + GTK.gtk_widget_set_margin_end(customIndicatorHandle, 4); + if ((style & SWT.CHECK) != 0) { + if (actionName != null) { + GTK4.gtk_actionable_set_action_name(customIndicatorHandle, Converter.javaStringToCString(actionName)); + } + } else { + GTK.gtk_widget_set_opacity(customIndicatorHandle, 0); + GTK.gtk_widget_set_visible(customIndicatorHandle, false); + } + GTK4.gtk_box_append(customLeadingBoxHandle, customIndicatorHandle); + customImageHandle = GTK.gtk_image_new(); + GTK.gtk_widget_set_margin_end(customImageHandle, 4); + GTK4.gtk_box_append(customLeadingBoxHandle, customImageHandle); + GTK4.gtk_box_append(hbox, customLeadingBoxHandle); + + customLabelHandle = GTK.gtk_label_new_with_mnemonic(null); + GTK.gtk_label_set_xalign(customLabelHandle, 0.0f); + // Expand the label so the accelerator is pushed to the trailing edge, as in a native row. + GTK.gtk_widget_set_hexpand(customLabelHandle, true); + GTK4.gtk_box_append(hbox, customLabelHandle); + + // Same "accelerator" CSS node as GtkModelButton's accel label, so the theme spaces it alike. + customAccelHandle = OS.g_object_new(GTK.gtk_label_get_type(), + Converter.javaStringToCString("css-name"), Converter.javaStringToCString("accelerator"), 0); + GTK.gtk_label_set_xalign(customAccelHandle, 1.0f); + GTK4.gtk_box_append(hbox, customAccelHandle); + + GTK4.gtk_button_set_child(customWidgetHandle, hbox); + updateCustomWidgetLabels(); + + if (actionName != null) { + GTK4.gtk_actionable_set_action_name(customWidgetHandle, Converter.javaStringToCString(actionName)); + } + + /* + * A plain GtkButton does not pop down its popover when clicked, so do it from + * gtk_clicked() (the action, dispatching SWT.Selection, has already run by then). + */ + display.addWidget(customWidgetHandle, this); + OS.g_signal_connect_closure(customWidgetHandle, OS.clicked, display.getClosure(CLICKED), false); + + /* + * A plain GtkButton does not close a sibling's open submenu on hover as + * GtkModelButton does; track enter/leave and mirror that from gtk4_enter_event(). + */ + long motionController = GTK4.gtk_event_controller_motion_new(); + OS.g_signal_connect(motionController, OS.enter, display.enterMotionProc, ENTER); + OS.g_signal_connect(motionController, OS.motion, display.enterMotionProc, MOTION); + OS.g_signal_connect(motionController, OS.leave, display.leaveProc, LEAVE); + GTK4.gtk_widget_add_controller(customWidgetHandle, motionController); + /* Give the custom row the "selected" look while focused, as GtkPopoverMenu does its native rows. */ + long focusController = GTK4.gtk_event_controller_focus_new(); + OS.g_signal_connect(focusController, OS.enter, display.focusProc, FOCUS_IN); + OS.g_signal_connect(focusController, OS.leave, display.focusProc, FOCUS_OUT); + GTK4.gtk_widget_add_controller(customWidgetHandle, focusController); + + // Mark this GMenuItem slot as "custom" so GtkPopoverMenu creates a placeholder. + // injectCustomWidgetGTK4() reassigns a fresh id before actually embedding. + reassignCustomId(); + parent.updateCustomRowGutters(); +} + +@Override +long gtk_clicked (long widget) { + /* Only ever connected on the custom menu row (GTK4), see createCustomMenuWidget. */ + long popover = getParentPopoverHandle(); + if (popover != 0) GTK.gtk_popover_popdown(popover); + return 0; +} + +/** + * Pointer entered the custom menu row (GTK4). Like GtkModelButton, close a sibling's + * open submenu once the pointer has rested here briefly; the delay lets a diagonal + * move from a CASCADE row into its submenu cross this row without closing it. + */ +@Override +void gtk4_enter_event(long controller, double x, double y, long event) { + customRowHovered = true; + setCustomRowSelected(true); + armSubmenuCloseTimer(); +} + +@Override +void gtk4_motion_event(long controller, double x, double y, long event) { + /* Like GtkModelButton, restart the delay on every motion: only a pointer that + * rests on the row closes the submenu, one merely passing through does not. */ + armSubmenuCloseTimer(); +} + +private void armSubmenuCloseTimer() { + int serial = ++hoverSerial; + display.timerExec(SUBMENU_CLOSE_DELAY, () -> { + if (serial != hoverSerial || !customRowHovered || isDisposed()) return; + // Hiding the submenu hands focus back and leaves a stray row selected; clear it. + // We do not grab focus for this row: focusing a row a model rebuild can invalidate + // risks a crash in GtkWindow's focus handling, and the pointer sync keeps it highlighted. + parent.hideOpenSubmenus(); + parent.clearStrayRowSelection(getParentPopoverHandle()); + // The focus hand-back is partly async, so sweep once more when it has settled. + display.timerExec(SUBMENU_CLOSE_DELAY * 3, () -> { + if (!isDisposed()) parent.clearStrayRowSelection(getParentPopoverHandle()); + }); + }); +} + +@Override +void gtk4_leave_event(long controller, long event) { + customRowHovered = false; + /* Drop the highlight unless the row still holds the keyboard focus. */ + if (!customRowFocused) setCustomRowSelected(false); +} + +@Override +void gtk4_focus_enter_event(long controller, long event) { + // Highlight the focused row like a focused native row. A stray focus (e.g. after + // a submenu hides) is swept by Menu.clearStrayRowSelection and the pointer sync. + customRowFocused = true; + setCustomRowSelected(true); +} + +@Override +void gtk4_focus_leave_event(long controller, long event) { + customRowFocused = false; + /* Keep the highlight if the pointer is still over the row. */ + if (!customRowHovered) setCustomRowSelected(false); +} + +/** + * GTK4 only: shows or hides the custom row's selection highlight - as the + * "selected" state (a11y and the native theme rule) and as the CSS class the + * display provider paints where a workbench theme suppresses the state (see + * Display.ensureMenuIconRowCss). + */ +void setCustomRowSelected(boolean selected) { + if (customWidgetHandle == 0) return; + if (((GTK.gtk_widget_get_state_flags(customWidgetHandle) & GTK.GTK_STATE_FLAG_SELECTED) != 0) == selected) return; + if (selected) { + GTK.gtk_widget_set_state_flags(customWidgetHandle, GTK.GTK_STATE_FLAG_SELECTED, false); + GTK.gtk_widget_add_css_class(customWidgetHandle, Converter.javaStringToCString(CUSTOM_SELECTED_CLASS)); + } else { + GTK.gtk_widget_unset_state_flags(customWidgetHandle, GTK.GTK_STATE_FLAG_SELECTED); + GTK.gtk_widget_remove_css_class(customWidgetHandle, Converter.javaStringToCString(CUSTOM_SELECTED_CLASS)); + } + // A widget in a custom popover slot does not repaint on its own style change, so + // force it - the class change would otherwise stay invisible until a later redraw. + GTK.gtk_widget_queue_draw(customWidgetHandle); +} + +/** + * Updates the custom widget's label and accelerator sub-labels from {@link #text}. + * SWT menu text carries the accelerator display after a tab (e.g. "Run\tCtrl+F11"); + * a GtkModelButton renders that via its "accel" property, but our custom GtkButton + * must render it explicitly in a trailing, right-aligned label. + */ +private void updateCustomWidgetLabels() { + if (customLabelHandle == 0) return; + String full = text != null ? text : ""; + String label = full; + String accel = ""; + int tab = full.indexOf('\t'); + if (tab != -1) { + label = full.substring(0, tab); + accel = full.substring(tab + 1); + } + char[] chars = fixMnemonic(label); + GTK.gtk_label_set_text_with_mnemonic(customLabelHandle, Converter.wcsToMbcs(chars, true)); + if (customAccelHandle != 0) { + GTK.gtk_label_set_text(customAccelHandle, Converter.wcsToMbcs(accel, true)); + GTK.gtk_widget_set_visible(customAccelHandle, !accel.isEmpty()); + } +} + +private void destroyCustomMenuWidget() { + // Clear the "custom" attribute so the next model rebuild shows a normal model button + OS.g_menu_item_set_attribute_value(handle, Converter.javaStringToCString("custom"), 0); + detachCustomMenuWidget(); + if (customWidgetHandle != 0) { + display.removeWidget(customWidgetHandle); + OS.g_object_unref(customWidgetHandle); + } + customWidgetHandle = 0; + customImageHandle = 0; + customLabelHandle = 0; + customAccelHandle = 0; + customIndicatorHandle = 0; + customLeadingBoxHandle = 0; + customId = null; +} + +/** + * Detaches an injected custom widget from its GtkPopoverMenu slot via + * gtk_popover_menu_remove_child() before its last reference is dropped. Detaching + * first matters: unreffing while GTK still holds the widget would leave the popover + * pointing at freed memory. + */ +private void detachCustomMenuWidget() { + if (customWidgetHandle == 0) return; + if (GTK.gtk_widget_get_parent(customWidgetHandle) == 0) return; + /* Walk up from the widget itself; the cached popover handle can be stale after a rebuild. */ + long popover = GTK.gtk_widget_get_parent(customWidgetHandle); + while (popover != 0 && !GTK4.GTK_IS_POPOVER_MENU(popover)) { + popover = GTK.gtk_widget_get_parent(popover); + } + if (popover != 0) { + GTK4.gtk_popover_menu_remove_child(popover, customWidgetHandle); + } + /* Defensive: if no owning popover was found, at least unparent. */ + if (GTK.gtk_widget_get_parent(customWidgetHandle) != 0) { + GTK.gtk_widget_unparent(customWidgetHandle); + } +} + +/** + * Pushes this item's current GMenuItem attributes into the parent GMenu by + * removing and re-inserting it (GMenu snapshots attributes on insertion). Injection + * is held off meanwhile: the removal's synchronous "items-changed" re-enters wiring, + * and while this item is out every later position is shifted, so a nested + * position-based refresh would remove the wrong item. + */ +void refreshMenuModelGTK4() { + boolean wasMutating = display.menuModelMutating; + display.menuModelMutating = true; + try { + OS.g_menu_remove(section.getSectionHandle(), section.getItemPosition(this)); + OS.g_menu_insert_item(section.getSectionHandle(), section.getItemPosition(this), handle); + } finally { + display.menuModelMutating = wasMutating; + } +} + +/** + * Ensures this item's custom widget is embedded into its GtkPopoverMenu slot. Safe + * to call repeatedly (e.g. every time the menu is shown). + * + * A GTK "custom" id is single-use: destroying the placeholder (on any model + * remove+reinsert) unparents our widget without freeing the id, and reusing that id + * hits "Duplicate custom ID" with no new slot, so the row renders empty. Hence + * "widget has no parent" triggers a (re)embed under a FRESH id: set the id, refresh + * the model so a new placeholder materialises, then embed. + */ +void injectCustomWidgetGTK4() { + if (customWidgetHandle == 0 || customId == null) return; + if (display.menuModelMutating) return; + long popoverHandle = getParentPopoverHandle(); + if (popoverHandle == 0) return; + // Already embedded in a live slot: nothing to do. + if (GTK.gtk_widget_get_parent(customWidgetHandle) != 0) return; + // Fresh id and a new placeholder; the previous id is burned in GTK's custom_slots. + reassignCustomId(); + refreshMenuModelGTK4(); + boolean added = GTK4.gtk_popover_menu_add_child(popoverHandle, customWidgetHandle, + Converter.javaStringToCString(customId)); + if (!added) { + // No placeholder slot was created (e.g. a cascade item, whose submenu link + // wins over the custom attribute). Fall back to a plain GtkModelButton so the + // item stays visible (label only, no icon) rather than an empty placeholder. + fallbackToModelButtonGTK4(); + } +} + +/** + * Assigns a fresh "custom" id to this item and writes it onto the GMenuItem's + * "custom" attribute, at creation and whenever a stale id must be replaced. + */ +private void reassignCustomId() { + customId = "swt-menu-" + (++customIdSeq); + long variant = OS.g_variant_new_string(Converter.javaStringToCString(customId)); + OS.g_menu_item_set_attribute_value(handle, Converter.javaStringToCString("custom"), variant); + // g_menu_item_set_attribute_value sinks the floating ref; do not unref variant +} + +/** + * Reverts this item to plain GtkModelButton rendering after a failed custom-widget + * injection: clears the "custom" attribute, releases the widget we built, and + * re-inserts the model item so GtkMenuTracker rebuilds it as a normal (visible) + * button. Prevents an un-injectable item from showing as an empty placeholder slot. + */ +private void fallbackToModelButtonGTK4() { + OS.g_menu_item_set_attribute_value(handle, Converter.javaStringToCString("custom"), 0); + if (customWidgetHandle != 0) { + display.removeWidget(customWidgetHandle); + OS.g_object_unref(customWidgetHandle); + customWidgetHandle = 0; + customImageHandle = 0; + customLabelHandle = 0; + customAccelHandle = 0; + customIndicatorHandle = 0; + customLeadingBoxHandle = 0; + customId = null; + } + refreshMenuModelGTK4(); +} + +long getParentPopoverHandle() { + if ((parent.style & SWT.POP_UP) != 0) { + return parent.handle; + } + return parent.popoverHandle; +} + /** * Sets the receiver's pull down menu to the argument. * Only CASCADE menu items can have a @@ -1185,8 +1616,7 @@ public void setMenu (Menu menu) { OS.g_menu_item_set_submenu(handle, 0); } - OS.g_menu_remove(section.getSectionHandle(), section.getItemPosition(this)); - OS.g_menu_insert_item(section.getSectionHandle(), section.getItemPosition(this), handle); + refreshMenuModelGTK4(); /* * If a DROP_DOWN is attached while its parent is already mapped (contributions @@ -1332,8 +1762,14 @@ public void setText (String string) { GTK.gtk_accelerator_name(maskKeysym.keysym, maskKeysym.mask) ); } - OS.g_menu_remove(section.getSectionHandle(), section.getItemPosition(this)); - OS.g_menu_insert_item(section.getSectionHandle(), section.getItemPosition(this), handle); + if (customWidgetHandle != 0) { + // The custom widget renders its own label; do not refresh the model position. + // GTK never forgets a registered "custom" id, so refreshing again here would + // create a duplicate, orphaned placeholder and drop the item from view. + updateCustomWidgetLabels(); + } else { + refreshMenuModelGTK4(); + } } else { if (labelHandle != 0 && GTK.GTK_IS_LABEL (labelHandle)) { GTK.gtk_label_set_text_with_mnemonic (labelHandle, buffer); diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java index 5ea3cbc497d..c8a512590b3 100644 --- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_MenuItem.java @@ -29,7 +29,6 @@ import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; /** @@ -208,7 +207,6 @@ public void test_setEnabledZ_cascade() { cascadeItem.dispose(); } -@Tag("gtk4-todo") @Override @Test public void test_setImageLorg_eclipse_swt_graphics_Image() {