gtk: Don't add text to GtkTextView until GTK has done initial layout
Pango >= 1.44 sometimes behaves unexpectedly when asked to fit text into
a space that is too small to be reasonable: when we give it the amount
of space that it previously said was adequate, it will sometimes ask for
more space. This leads to GTK 2 going into an infinite loop of redoing
the layout (#987587) as the combination of GTK and Pango flaps between
two size requests.
It isn't clear whether this is a bug in GTK 2 or Pango or Debconf,
but we can dodge it by making sure GTK gets a chance to decide on the
layout before we add text, so that initial text wrapping is done at a
more reasonable width.
Signed-off-by: Simon McVittie <smcv@debian.org>
Closes: #988787
Simon McVittie
2 years ago
122 | 122 | /** vertical padding around descriptions */ |
123 | 123 | #define DESCRIPTION_VPADDING 3 |
124 | 124 | |
125 | typedef struct | |
126 | { | |
127 | GtkTextView *view; | |
128 | char *text; | |
129 | gboolean italic; | |
130 | } SetTextLater; | |
131 | ||
132 | static void | |
133 | set_text_later_free(gpointer p) | |
134 | { | |
135 | SetTextLater *data = p; | |
136 | ||
137 | g_return_if_fail (data != NULL); | |
138 | g_clear_object(&data->view); | |
139 | g_clear_pointer(&data->text, g_free); | |
140 | g_free(data); | |
141 | } | |
142 | ||
143 | static gboolean | |
144 | set_text_in_idle(gpointer user_data) | |
145 | { | |
146 | SetTextLater *data = user_data; | |
147 | GtkTextBuffer *buffer; | |
148 | ||
149 | g_return_val_if_fail(data != NULL, G_SOURCE_REMOVE); | |
150 | g_return_val_if_fail(GTK_IS_TEXT_VIEW(data->view), G_SOURCE_REMOVE); | |
151 | ||
152 | buffer = gtk_text_view_get_buffer(data->view); | |
153 | ||
154 | gtk_text_buffer_set_text(buffer, data->text, -1 /* until '\0' */); | |
155 | ||
156 | if (data->italic) { | |
157 | GtkTextIter start; | |
158 | GtkTextIter end; | |
159 | ||
160 | gtk_text_buffer_create_tag(buffer, "italic", "style", | |
161 | PANGO_STYLE_ITALIC, NULL); | |
162 | gtk_text_buffer_get_start_iter(buffer, &start); | |
163 | gtk_text_buffer_get_end_iter(buffer, &end); | |
164 | gtk_text_buffer_apply_tag_by_name(buffer, "italic", &start, &end); | |
165 | } | |
166 | ||
167 | return G_SOURCE_REMOVE; | |
168 | } | |
169 | ||
170 | /* Takes ownership of text */ | |
171 | static void | |
172 | set_text_later (GtkTextView *view, | |
173 | char *text, | |
174 | gboolean italic) | |
175 | { | |
176 | SetTextLater *data = g_new0 (SetTextLater, 1); | |
177 | ||
178 | /* This is a workaround for a relayout loop in debian-installer, | |
179 | * where nowhere near enough space is initially allocated for the text, | |
180 | * leading to Pango giving strange answers when asked how much space it | |
181 | * needs, and GTK 2 constantly redoing the layout when Pango flaps | |
182 | * between two answers. If we do the layout first and add the text | |
183 | * afterwards, then GTK already knows it has plenty of space for the | |
184 | * text and doesn't need to ask Pango for a worst-case scenario. | |
185 | * | |
186 | * This workaround will only work if we let GTK do its layout first, | |
187 | * so the priority used for g_main_context_invoke_full below must be | |
188 | * something less urgent (numerically larger) than GTK_PRIORITY_RESIZE. */ | |
189 | G_STATIC_ASSERT (G_PRIORITY_DEFAULT_IDLE > GTK_PRIORITY_RESIZE); | |
190 | ||
191 | g_return_if_fail(view != NULL); | |
192 | g_return_if_fail(text != NULL); | |
193 | ||
194 | data->view = g_object_ref (view); | |
195 | data->text = text; | |
196 | data->italic = italic; | |
197 | g_main_context_invoke_full (NULL, G_PRIORITY_DEFAULT_IDLE, | |
198 | set_text_in_idle, data, set_text_later_free); | |
199 | } | |
200 | ||
125 | 201 | /** Add a description to a given container. |
126 | 202 | * |
127 | 203 | * @param fe cdebconf frontend |
132 | 208 | GtkWidget * container) |
133 | 209 | { |
134 | 210 | GtkWidget * view; |
135 | GtkTextBuffer * buffer; | |
136 | GtkTextIter start; | |
137 | GtkTextIter end; | |
138 | 211 | char * description; |
139 | 212 | |
140 | 213 | description = q_get_description(fe, question); |
141 | 214 | view = gtk_text_view_new(); |
142 | buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); | |
143 | gtk_text_buffer_set_text(buffer, description, -1 /* until '\0' */); | |
144 | g_free(description); | |
215 | set_text_later(GTK_TEXT_VIEW(view), g_steal_pointer (&description), | |
216 | TRUE /* italic */); | |
145 | 217 | gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); |
146 | 218 | gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); |
147 | 219 | gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR); |
148 | 220 | gtk_text_view_set_left_margin(GTK_TEXT_VIEW(view), DESCRIPTION_MARGIN); |
149 | 221 | gtk_text_view_set_right_margin(GTK_TEXT_VIEW(view), DESCRIPTION_MARGIN); |
150 | gtk_text_buffer_create_tag(buffer, "italic", "style", | |
151 | PANGO_STYLE_ITALIC, NULL); | |
152 | gtk_text_buffer_get_start_iter(buffer, &start); | |
153 | gtk_text_buffer_get_end_iter(buffer, &end); | |
154 | gtk_text_buffer_apply_tag_by_name(buffer, "italic", &start, &end); | |
155 | 222 | gtk_widget_modify_base(view, GTK_STATE_NORMAL, get_background_color(fe)); |
156 | 223 | gtk_box_pack_start(GTK_BOX(container), view, |
157 | 224 | FALSE /* don't expand */, FALSE /* don't fill */, |
173 | 240 | struct question * question, |
174 | 241 | GtkWidget * container) |
175 | 242 | { |
176 | GtkTextBuffer * buffer; | |
177 | 243 | GtkWidget * view; |
178 | 244 | char * ext_description; |
179 | 245 | |
183 | 249 | ext_description = q_get_extended_description(fe, question); |
184 | 250 | if ('\0' != ext_description[0]) { |
185 | 251 | view = gtk_text_view_new(); |
186 | buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view)); | |
187 | gtk_text_buffer_set_text(buffer, ext_description, -1); | |
252 | set_text_later(GTK_TEXT_VIEW(view), g_steal_pointer (&ext_description), | |
253 | FALSE /* italic */); | |
188 | 254 | gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); |
189 | 255 | gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); |
190 | 256 | gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD_CHAR); |