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 0d22afe950..75e8d47877 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 @@ -796,6 +796,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) @@ -2260,6 +2274,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) 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 6766ff954b..00d55fcb1b 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 @@ -83,6 +83,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, @@ -185,6 +186,7 @@ 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, 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 24178bd35c..3a11b3f46f 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 @@ -11984,6 +11984,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) 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 f30ca89d0a..c9585a87c9 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 @@ -969,6 +969,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, 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 aa70f5a3f2..98340cf182 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 @@ -2416,6 +2416,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 ba03262b09..a67863b337 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 @@ -75,6 +75,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 *) @@ -588,6 +595,12 @@ 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 model cast=(GMenuModel *) * @param flags cast=(GtkPopoverMenuFlags) 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 3dc45318bb..781810c48a 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 @@ -864,7 +864,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); } } @@ -886,6 +888,7 @@ private void connectDropDownMenuSignals() { display.addWidget(popover, menuItem.menu); 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); + menuItem.menu.injectCustomMenuIcons(); /* * Also connect SHOW/HIDE signals for nested CASCADE submenus. */ @@ -917,6 +920,7 @@ private void connectCascadeSubMenuSignals(Menu menu, long parentPopoverHandle) { display.addWidget(nestedPopover, item.menu); OS.g_signal_connect_closure_by_id(nestedPopover, display.signalIds[SHOW], 0, display.getClosure(SHOW), false); OS.g_signal_connect_closure_by_id(nestedPopover, display.signalIds[HIDE], 0, display.getClosure(HIDE), false); + item.menu.injectCustomMenuIcons(); // Recurse to handle further nested CASCADE submenus connectCascadeSubMenuSignals(item.menu); } @@ -924,6 +928,15 @@ private void connectCascadeSubMenuSignals(Menu menu, long parentPopoverHandle) { } } +void injectCustomMenuIcons() { + if (items == null) return; + for (MenuItem item : items) { + if ((item.style & SWT.SEPARATOR) == 0 && item.customWidgetHandle != 0) { + item.injectCustomWidgetGTK4(); + } + } +} + private long findNestedPopoverForModel(long parentWidget, long targetModel) { if (parentWidget == 0 || targetModel == 0) return 0; long child = GTK4.gtk_widget_get_first_child(parentWidget); @@ -985,6 +998,7 @@ long gtk_show (long widget) { sendEvent (SWT.Show); /* Retry cascade submenu signal hookup 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) { 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 e98a8b0229..2b40e8331f 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 @@ -69,6 +69,12 @@ public class MenuItem extends Item { long modelHandle, actionHandle, shortcutHandle; Section section; String actionName; + /** GTK4 only: custom widget for icon+text display in GtkPopoverMenu */ + long customWidgetHandle, customImageHandle, customLabelHandle, customAccelHandle; + String customId; + boolean customWidgetInjected; + private boolean customWidgetInjecting; + private static int customIdSeq = 0; /** * Constructs a new instance of this class given its parent @@ -517,7 +523,7 @@ public boolean getEnabled () { checkWidget(); if (GTK.GTK4) { - if ((style & SWT.CASCADE) != 0) { + if ((style & SWT.CASCADE) != 0 || actionHandle == 0) { return true; } @@ -744,6 +750,15 @@ void releaseWidget() { if (GTK.GTK4) { if (parent.actionGroup != 0 && actionName != null) OS.g_action_map_remove_action(parent.actionGroup, Converter.javaStringToCString(actionName)); + if (customWidgetHandle != 0) { + detachCustomMenuWidget(); + OS.g_object_unref(customWidgetHandle); + customWidgetHandle = 0; + customImageHandle = 0; + customLabelHandle = 0; + customAccelHandle = 0; + customId = null; + } } else { long accelGroup = getAccelGroup(); if (accelGroup != 0) removeAccelerator(accelGroup); @@ -1021,7 +1036,7 @@ public void setID (int id) { *
* Note: This operation is a HINT and is not supported on * platforms that do not have this concept (for example, Windows NT). - * Some platforms (such as GTK3) support images alongside check boxes. + * Some platforms (such as GTK3 and GTK4) support images alongside check boxes. *
* * @param image the image to display on the receiver (may be null) @@ -1033,9 +1048,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; @@ -1046,6 +1058,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 (); @@ -1091,6 +1107,225 @@ private void _setImage (Image image) { } } +private void _setImageGTK4(Image image) { + // Only plain PUSH items use the custom icon+label widget. Other styles must keep + // their default GtkModelButton rendering: + // - CASCADE: GtkMenuSectionBox checks the submenu link before the "custom" attribute, + // so a cascade item never gets a placeholder slot for a custom widget. + // - CHECK / RADIO: the custom GtkButton cannot draw the check/radio selection + // indicator that GtkModelButton derives from the action state, so using it would + // hide the item's selection. GtkModelButton hides the icon when it has text anyway, + // so these items are label + indicator only (no icon) on GTK4. + if ((style & (SWT.CASCADE | SWT.CHECK | 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) { + // Attempt injection now in case the popover already exists (item + // added to an already-open menu). If it is not ready yet, injection + // is retried - and self-heals - from Menu.injectCustomMenuIcons() + // when the menu is shown. + injectCustomWidgetGTK4(); + } + } + } + } else { + if (customWidgetHandle != 0) { + destroyCustomMenuWidget(); + refreshMenuModelGTK4(); + } + } +} + +private void createCustomMenuWidget() { + customWidgetHandle = GTK.gtk_button_new(); + OS.g_object_ref_sink(customWidgetHandle); + GTK.gtk_widget_add_css_class(customWidgetHandle, Converter.javaStringToCString("flat")); + + long hbox = GTK.gtk_box_new(GTK.GTK_ORIENTATION_HORIZONTAL, 6); + customImageHandle = GTK.gtk_image_new(); + GTK4.gtk_box_append(hbox, customImageHandle); + + customLabelHandle = GTK.gtk_label_new_with_mnemonic(null); + GTK.gtk_label_set_xalign(customLabelHandle, 0.0f); + // Expand the label so the accelerator label is pushed to the trailing edge, + // matching the layout of a normal GtkModelButton menu row. + GTK.gtk_widget_set_hexpand(customLabelHandle, true); + GTK4.gtk_box_append(hbox, customLabelHandle); + + customAccelHandle = GTK.gtk_label_new(null); + GTK.gtk_label_set_xalign(customAccelHandle, 1.0f); + GTK.gtk_widget_add_css_class(customAccelHandle, Converter.javaStringToCString("dim-label")); + 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)); + } + + // Mark this GMenuItem slot as "custom" so GtkPopoverMenu creates a placeholder. + // injectCustomWidgetGTK4() reassigns a fresh id before actually embedding. + reassignCustomId(); +} + +/** + * 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) { + OS.g_object_unref(customWidgetHandle); + } + customWidgetHandle = 0; + customImageHandle = 0; + customLabelHandle = 0; + customAccelHandle = 0; + customId = null; + customWidgetInjected = false; +} + +/** + * Cleanly unparents an injected custom widget from its GtkPopoverMenu placeholder + * slot before the widget's last reference is + * dropped. GTK's GtkMenuSectionBox never removes the placeholder's "custom" id + * from its internal hash table, and the gizmo placeholder holds its own + * parent-child reference on the child widget: unref'ing the widget without first + * unparenting it here would drop what may be its only remaining reference while + * GTK still has it linked into the popover's widget tree, corrupting layout and + * producing "gtk_widget_unparent: assertion 'GTK_IS_WIDGET (widget)' failed". + */ +private void detachCustomMenuWidget() { + if (customWidgetHandle != 0) { + // Equivalent to gtk_popover_menu_remove_child(): the widget is parented into + // its placeholder gizmo slot, so unparent it while it is still valid. Guard on + // having a parent to avoid the "gtk_widget_unparent: assertion 'GTK_IS_WIDGET + // (widget)' failed" that fires when unparenting an already-detached widget. + if (GTK.gtk_widget_get_parent(customWidgetHandle) != 0) { + GTK.gtk_widget_unparent(customWidgetHandle); + } + customWidgetInjected = false; + } +} + +void refreshMenuModelGTK4() { + OS.g_menu_remove(section.getSectionHandle(), section.getItemPosition(this)); + OS.g_menu_insert_item(section.getSectionHandle(), section.getItemPosition(this), handle); +} + +/** + * Ensures this item's custom icon+label widget is embedded into its GtkPopoverMenu + * placeholder slot. Safe to call repeatedly (e.g. every time the menu is shown). + * + * A GTK "custom" id is single-use: GtkMenuSectionBox keeps every registered id in an + * append-only hash table, and destroying the placeholder (which happens whenever the + * model item is removed+reinserted - by separator/section restructuring, cascade + * changes, etc.) unparents our widget without freeing the id. Reinserting under the + * same id then hits "Duplicate custom ID" and no new slot is created, orphaning the + * widget so the row renders empty. We therefore treat "widget currently has no + * parent" as the trigger to (re)establish it under a FRESH id: set the new id, refresh + * the model so GtkMenuTracker materialises a new placeholder, then embed the widget. + */ +void injectCustomWidgetGTK4() { + if (customWidgetInjecting) return; + if (customWidgetHandle == 0 || customId == null) 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; + customWidgetInjecting = true; + try { + // Mint a fresh id and rematerialise the placeholder. The previous id (if any) + // is now burned in GTK's custom_slots and cannot be reused. + reassignCustomId(); + refreshMenuModelGTK4(); + boolean added = GTK4.gtk_popover_menu_add_child(popoverHandle, customWidgetHandle, + Converter.javaStringToCString(customId)); + customWidgetInjected = added; + 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(); + } + } finally { + customWidgetInjecting = false; + } +} + +/** + * Assigns a fresh, never-before-used "custom" id to this item and writes it onto the + * GMenuItem's "custom" attribute. Used both at creation and whenever a stale id must + * be replaced (see {@link #injectCustomWidgetGTK4()}). + */ +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) { + OS.g_object_unref(customWidgetHandle); + customWidgetHandle = 0; + customImageHandle = 0; + customLabelHandle = 0; + customAccelHandle = 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. * OnlyCASCADE menu items can have a
@@ -1281,8 +1516,15 @@ 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 {
+ OS.g_menu_remove(section.getSectionHandle(), section.getItemPosition(this));
+ OS.g_menu_insert_item(section.getSectionHandle(), section.getItemPosition(this), handle);
+ }
} 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 1db0b857ec..d0d8cb9cfa 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;
/**
@@ -195,7 +194,6 @@ public void test_setEnabledZ() {
assertFalse(menuItem.getEnabled());
}
-@Tag("gtk4-todo")
@Override
@Test
public void test_setImageLorg_eclipse_swt_graphics_Image() {