From d37bd9ba42bc3723cae0d61396ea04319319c3f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 17:21:29 +0000 Subject: [PATCH 1/4] [GTK] Speed up Combo setItems/removeAll for large item counts Setting or removing a large number of combo items (>5000) was very slow on GTK because GtkComboBox recomputes its popup/cell-view layout on every single model change, leading to O(n^2)/O(n) per-row overhead. Detach the GtkListStore model from the combo box before bulk modifying it and re-attach it afterwards, so the widget reacts only once. setItems now also populates the GtkListStore directly instead of going through the per-item GtkComboBoxText convenience function. Adds the gtk_combo_box_set_model native binding. Fixes https://github.com/eclipse-platform/eclipse.platform.swt/issues/506 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Suf4gTtBDjKBbFBzJd1mVr --- .../gtk/org/eclipse/swt/internal/gtk/GTK.java | 6 +- .../gtk/org/eclipse/swt/widgets/Combo.java | 67 ++++++++++++++++--- 2 files changed, 61 insertions(+), 12 deletions(-) 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..4d7f140abc1 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 @@ -528,7 +528,6 @@ public class GTK extends OS { /** * @param combo_box cast=(GtkComboBoxText *) */ - /* Do not call directly. Call Combo.gtk_combo_box_text_remove_all(..) instead). */ public static final native void gtk_combo_box_text_remove_all(long combo_box); /** * @param combo_box cast=(GtkComboBox *) @@ -540,6 +539,11 @@ public class GTK extends OS { public static final native long gtk_combo_box_get_model(long combo_box); /** * @param combo_box cast=(GtkComboBox *) + * @param model cast=(GtkTreeModel *) + */ + public static final native void gtk_combo_box_set_model(long combo_box, long model); + /** + * @param combo_box cast=(GtkComboBox *) * @param index cast=(gint) */ public static final native void gtk_combo_box_set_active(long combo_box, int index); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java index d58fed8720e..3deeadf6611 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java @@ -2112,7 +2112,23 @@ public void removeAll () { items = new String[0]; clearText(); - gtk_combo_box_text_remove_all(); + + long model = handle != 0 ? GTK.gtk_combo_box_get_model (handle) : 0; + if (model != 0) { + /* + * Bug 506: Removing a large number of combo items is slow because the + * GtkComboBox reacts to every single row deletion. Detach the model + * from the combo box, clear the GtkListStore in one step, and re-attach + * it so the widget only updates once. + */ + OS.g_object_ref (model); + gtk_combo_box_toggle_wrap (false); + GTK.gtk_combo_box_set_model (handle, 0); + GTK.gtk_list_store_clear (model); + GTK.gtk_combo_box_set_model (handle, model); + OS.g_object_unref (model); + gtk_combo_box_toggle_wrap (true); + } } /** @@ -2401,20 +2417,49 @@ public void setItems (String... items) { System.arraycopy (items, 0, this.items, 0, items.length); clearText (); - gtk_combo_box_text_remove_all(); - for (int i = 0; i < items.length; i++) { - String string = items [i]; - gtk_combo_box_insert(string, i); - if ((style & SWT.RIGHT_TO_LEFT) != 0 && popupHandle != 0) { - GTK3.gtk_container_forall (popupHandle, display.setDirectionProc, GTK.GTK_TEXT_DIR_RTL); + long model = handle != 0 ? GTK.gtk_combo_box_get_model (handle) : 0; + if (model != 0) { + /* + * Bug 506: Setting a large number of combo items is slow because the + * GtkComboBox recomputes its popup/cell-view layout on every single + * model change, resulting in O(n^2) behavior. The fix is to temporarily + * detach the model from the combo box, populate the GtkListStore + * directly, and then re-attach the model. This way the widget reacts + * only once instead of once per inserted item. + */ + OS.g_object_ref (model); + gtk_combo_box_toggle_wrap (false); + GTK.gtk_combo_box_set_model (handle, 0); + GTK.gtk_list_store_clear (model); + for (int i = 0; i < items.length; i++) { + gtk_list_store_insert (model, items [i], i); } + GTK.gtk_combo_box_set_model (handle, model); + OS.g_object_unref (model); + gtk_combo_box_toggle_wrap (true); + } + + if ((style & SWT.RIGHT_TO_LEFT) != 0 && popupHandle != 0) { + GTK3.gtk_container_forall (popupHandle, display.setDirectionProc, GTK.GTK_TEXT_DIR_RTL); } } -private void gtk_combo_box_text_remove_all() { - gtk_combo_box_toggle_wrap(false); - if (handle != 0) GTK.gtk_combo_box_text_remove_all(handle); - gtk_combo_box_toggle_wrap(true); +/** + * Inserts an item directly into the combo's underlying GtkListStore, bypassing + * the GtkComboBoxText convenience functions (which require the model to be + * attached to the combo box). This is used during bulk operations while the + * model is temporarily detached, see {@link #setItems}. + *

+ * The text of a GtkComboBoxText is stored in column 0 of its model, matching + * the cell renderer attribute configured in {@code createHandle}. + *

+ */ +private void gtk_list_store_insert (long model, String string, int index) { + byte[] buffer = Converter.wcsToMbcs (string, true); + long iter = OS.g_malloc (GTK.GtkTreeIter_sizeof ()); + GTK.gtk_list_store_insert (model, iter, index); + GTK.gtk_list_store_set (model, iter, 0, buffer, -1); + OS.g_free (iter); } /** From 21ee7bf906bce1dbbda0f80f9f8f7c76898f65f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 17:27:01 +0000 Subject: [PATCH 2/4] [GTK] Speed up Combo.remove(start, end) for large ranges Apply the same model-detach optimization as setItems/removeAll to the range-remove path: detach the GtkListStore, remove the rows directly from the store (from the end downwards to keep indices stable), then re-attach so GtkComboBox recomputes its layout only once instead of per removed row. Refs https://github.com/eclipse-platform/eclipse.platform.swt/issues/506 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Suf4gTtBDjKBbFBzJd1mVr --- .../gtk/org/eclipse/swt/widgets/Combo.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java index 3deeadf6611..6fd1381db26 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java @@ -2068,11 +2068,29 @@ public void remove (int start, int end) { int index = GTK.gtk_combo_box_get_active (handle); if (start <= index && index <= end) clearText(); - gtk_combo_box_toggle_wrap(false); - for (int i = end; i >= start; i--) { - if (handle != 0) GTK.gtk_combo_box_text_remove(handle, i); + long model = handle != 0 ? GTK.gtk_combo_box_get_model (handle) : 0; + if (model != 0) { + /* + * Bug 506: Removing a range of items one-by-one is slow because the + * GtkComboBox reacts to every single row deletion. Detach the model, + * remove the rows directly from the GtkListStore (from the end of the + * range downwards so indices stay stable), and re-attach it so the + * widget only updates once. + */ + OS.g_object_ref (model); + gtk_combo_box_toggle_wrap (false); + GTK.gtk_combo_box_set_model (handle, 0); + long iter = OS.g_malloc (GTK.GtkTreeIter_sizeof ()); + for (int i = end; i >= start; i--) { + if (GTK.gtk_tree_model_iter_nth_child (model, iter, 0, i)) { + GTK.gtk_list_store_remove (model, iter); + } + } + OS.g_free (iter); + GTK.gtk_combo_box_set_model (handle, model); + OS.g_object_unref (model); + gtk_combo_box_toggle_wrap (true); } - gtk_combo_box_toggle_wrap(true); } /** From eaf490885f0107ecc4c44e678420ce02a8da2137 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 17:33:00 +0000 Subject: [PATCH 3/4] [GTK] Address review: restore Combo selection and reuse iter - remove(start, end): restore the active selection after the model is re-attached. Detaching the model resets the active item to -1, so a selection outside the removed range would otherwise be lost. The index is adjusted for the removed rows and restored with the CHANGED signal blocked to avoid a spurious Modify event, mirroring select(int). - setItems: allocate the GtkTreeIter once and reuse it across the insert loop instead of allocating/freeing per item. Refs https://github.com/eclipse-platform/eclipse.platform.swt/issues/506 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Suf4gTtBDjKBbFBzJd1mVr --- .../gtk/org/eclipse/swt/widgets/Combo.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java index 6fd1381db26..77866968a01 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java @@ -2067,6 +2067,15 @@ public void remove (int start, int end) { items = newItems; int index = GTK.gtk_combo_box_get_active (handle); if (start <= index && index <= end) clearText(); + /* + * Detaching the model below resets the active item to -1. If the previously + * selected item is outside the removed range it must be restored afterwards, + * adjusted for the rows removed before it. + */ + int newIndex = -1; + if (index != -1 && !(start <= index && index <= end)) { + newIndex = index > end ? index - (end - start + 1) : index; + } long model = handle != 0 ? GTK.gtk_combo_box_get_model (handle) : 0; if (model != 0) { @@ -2088,6 +2097,12 @@ public void remove (int start, int end) { } OS.g_free (iter); GTK.gtk_combo_box_set_model (handle, model); + if (newIndex != -1) { + // Restore the selection lost by detaching the model, without firing a spurious Modify event. + OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED); + GTK.gtk_combo_box_set_active (handle, newIndex); + OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, CHANGED); + } OS.g_object_unref (model); gtk_combo_box_toggle_wrap (true); } @@ -2449,9 +2464,11 @@ public void setItems (String... items) { gtk_combo_box_toggle_wrap (false); GTK.gtk_combo_box_set_model (handle, 0); GTK.gtk_list_store_clear (model); + long iter = OS.g_malloc (GTK.GtkTreeIter_sizeof ()); for (int i = 0; i < items.length; i++) { - gtk_list_store_insert (model, items [i], i); + gtk_list_store_insert (model, iter, items [i], i); } + OS.g_free (iter); GTK.gtk_combo_box_set_model (handle, model); OS.g_object_unref (model); gtk_combo_box_toggle_wrap (true); @@ -2469,15 +2486,14 @@ public void setItems (String... items) { * model is temporarily detached, see {@link #setItems}. *

* The text of a GtkComboBoxText is stored in column 0 of its model, matching - * the cell renderer attribute configured in {@code createHandle}. + * the cell renderer attribute configured in {@code createHandle}. The caller + * passes a single reusable {@code iter} to avoid per-item allocation overhead. *

*/ -private void gtk_list_store_insert (long model, String string, int index) { +private void gtk_list_store_insert (long model, long iter, String string, int index) { byte[] buffer = Converter.wcsToMbcs (string, true); - long iter = OS.g_malloc (GTK.GtkTreeIter_sizeof ()); GTK.gtk_list_store_insert (model, iter, index); GTK.gtk_list_store_set (model, iter, 0, buffer, -1); - OS.g_free (iter); } /** From ab7962abca85736ebce804573c8c9e3cac75d19d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 17:49:55 +0000 Subject: [PATCH 4/4] [GTK] Regenerate JNI bindings for gtk_combo_box_set_model The CI native build compiles the committed generated JNI glue rather than regenerating it from the Java sources, so adding the gtk_combo_box_set_model binding to GTK.java alone caused UnsatisfiedLinkError in the Combo tests. Add the corresponding generated entries (os.c JNI function and the os_stats.h function enum) so the native library exports the symbol. The function is available on both GTK3 and GTK4. Refs https://github.com/eclipse-platform/eclipse.platform.swt/issues/506 Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Suf4gTtBDjKBbFBzJd1mVr --- .../org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c | 10 ++++++++++ .../Eclipse SWT PI/gtk/library/os_stats.h | 1 + 2 files changed, 11 insertions(+) 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 58a716c597e..2d4ac759a1e 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 @@ -4186,6 +4186,16 @@ JNIEXPORT void JNICALL GTK_NATIVE(gtk_1combo_1box_1set_1active) } #endif +#ifndef NO_gtk_1combo_1box_1set_1model +JNIEXPORT void JNICALL GTK_NATIVE(gtk_1combo_1box_1set_1model) + (JNIEnv *env, jclass that, jlong arg0, jlong arg1) +{ + GTK_NATIVE_ENTER(env, that, gtk_1combo_1box_1set_1model_FUNC); + gtk_combo_box_set_model((GtkComboBox *)arg0, (GtkTreeModel *)arg1); + GTK_NATIVE_EXIT(env, that, gtk_1combo_1box_1set_1model_FUNC); +} +#endif + #ifndef NO_gtk_1combo_1box_1text_1insert JNIEXPORT void JNICALL GTK_NATIVE(gtk_1combo_1box_1text_1insert) (JNIEnv *env, jclass that, jlong arg0, jint arg1, jbyteArray arg2, jbyteArray arg3) 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 442262a9d75..95a9509c9c0 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 @@ -328,6 +328,7 @@ typedef enum { gtk_1combo_1box_1popdown_FUNC, gtk_1combo_1box_1popup_FUNC, gtk_1combo_1box_1set_1active_FUNC, + gtk_1combo_1box_1set_1model_FUNC, gtk_1combo_1box_1text_1insert_FUNC, gtk_1combo_1box_1text_1new_FUNC, gtk_1combo_1box_1text_1new_1with_1entry_FUNC,