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, 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..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,12 +2067,45 @@ 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; + } - 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); + 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); } - gtk_combo_box_toggle_wrap(true); } /** @@ -2112,7 +2145,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 +2450,50 @@ 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); + long iter = OS.g_malloc (GTK.GtkTreeIter_sizeof ()); + for (int i = 0; i < items.length; 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); + } + + 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}. The caller + * passes a single reusable {@code iter} to avoid per-item allocation overhead. + *
+ */ +private void gtk_list_store_insert (long model, long iter, String string, int index) { + byte[] buffer = Converter.wcsToMbcs (string, true); + GTK.gtk_list_store_insert (model, iter, index); + GTK.gtk_list_store_set (model, iter, 0, buffer, -1); } /**