Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions bundles/org.eclipse.swt/Eclipse SWT PI/gtk/library/os.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 *)
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Comment on lines 2068 to 2108

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When the model is detached from the GtkComboBox using GTK.gtk_combo_box_set_model(handle, 0), the active selection index is reset to -1 (none) by GTK. When the model is re-attached, the selection is not automatically restored, resulting in a loss of the user's selection if the active item was not in the removed range.

To fix this regression, we should calculate the adjusted active index before detaching the model, and restore it (while blocking the CHANGED signal to avoid redundant events) after re-attaching the model.

	int index = GTK.gtk_combo_box_get_active (handle);
	if (start <= index && index <= end) clearText();
	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) {
		/*
		 * 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) {
			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);
}

/**
Expand Down Expand Up @@ -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);
}
}

/**
Expand Down Expand Up @@ -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);
Comment on lines +2463 to +2474

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To optimize bulk insertions, we can allocate the GtkTreeIter once outside the loop and reuse it, rather than allocating and freeing it for every single item inside gtk_list_store_insert. This significantly reduces native memory allocation overhead (g_malloc / g_free) when setting a large number of items.

		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}.
* <p>
* 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.
* </p>
*/
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);
}

/**
Expand Down
Loading