-
Notifications
You must be signed in to change notification settings - Fork 0
[GTK] Speed up Combo setItems/removeAll/remove for large item counts #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d37bd9b
21ee7bf
eaf4908
ab7962a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Comment on lines
+2463
to
+2474
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To optimize bulk insertions, we can allocate the 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); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the model is detached from the
GtkComboBoxusingGTK.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
CHANGEDsignal to avoid redundant events) after re-attaching the model.