1 | /* GTK - The GIMP Toolkit |
2 | * |
3 | * Copyright (C) 2010 Christian Dywan |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Lesser General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Lesser General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Lesser General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | #include "config.h" |
20 | |
21 | #include "gtkcomboboxtext.h" |
22 | #include "gtkcombobox.h" |
23 | #include "gtkcellrenderertext.h" |
24 | #include "gtkcelllayout.h" |
25 | #include "gtkbuildable.h" |
26 | #include "gtkbuilderprivate.h" |
27 | #include "gtkliststore.h" |
28 | |
29 | #include <string.h> |
30 | |
31 | /** |
32 | * GtkComboBoxText: |
33 | * |
34 | * A `GtkComboBoxText` is a simple variant of `GtkComboBox` for text-only |
35 | * use cases. |
36 | * |
37 | *  |
38 | * |
39 | * `GtkComboBoxText` hides the model-view complexity of `GtkComboBox`. |
40 | * |
41 | * To create a `GtkComboBoxText`, use [ctor@Gtk.ComboBoxText.new] or |
42 | * [ctor@Gtk.ComboBoxText.new_with_entry]. |
43 | * |
44 | * You can add items to a `GtkComboBoxText` with |
45 | * [method@Gtk.ComboBoxText.append_text], |
46 | * [method@Gtk.ComboBoxText.insert_text] or |
47 | * [method@Gtk.ComboBoxText.prepend_text] and remove options with |
48 | * [method@Gtk.ComboBoxText.remove]. |
49 | * |
50 | * If the `GtkComboBoxText` contains an entry (via the |
51 | * [property@Gtk.ComboBox:has-entry] property), its contents can be retrieved |
52 | * using [method@Gtk.ComboBoxText.get_active_text]. |
53 | * |
54 | * You should not call [method@Gtk.ComboBox.set_model] or attempt to pack more |
55 | * cells into this combo box via its [iface@Gtk.CellLayout] interface. |
56 | * |
57 | * # GtkComboBoxText as GtkBuildable |
58 | * |
59 | * The `GtkComboBoxText` implementation of the `GtkBuildable` interface supports |
60 | * adding items directly using the <items> element and specifying <item> |
61 | * elements for each item. Each <item> element can specify the “id” |
62 | * corresponding to the appended text and also supports the regular |
63 | * translation attributes “translatable”, “context” and “comments”. |
64 | * |
65 | * Here is a UI definition fragment specifying `GtkComboBoxText` items: |
66 | * ```xml |
67 | * <object class="GtkComboBoxText"> |
68 | * <items> |
69 | * <item translatable="yes" id="factory">Factory</item> |
70 | * <item translatable="yes" id="home">Home</item> |
71 | * <item translatable="yes" id="subway">Subway</item> |
72 | * </items> |
73 | * </object> |
74 | * ``` |
75 | * |
76 | * # CSS nodes |
77 | * |
78 | * ``` |
79 | * combobox |
80 | * ╰── box.linked |
81 | * ├── entry.combo |
82 | * ├── button.combo |
83 | * ╰── window.popup |
84 | * ``` |
85 | * |
86 | * `GtkComboBoxText` has a single CSS node with name combobox. It adds |
87 | * the style class .combo to the main CSS nodes of its entry and button |
88 | * children, and the .linked class to the node of its internal box. |
89 | */ |
90 | |
91 | typedef struct _GtkComboBoxTextClass GtkComboBoxTextClass; |
92 | |
93 | struct _GtkComboBoxText |
94 | { |
95 | GtkComboBox parent_instance; |
96 | }; |
97 | |
98 | struct _GtkComboBoxTextClass |
99 | { |
100 | GtkComboBoxClass parent_class; |
101 | }; |
102 | |
103 | |
104 | static void gtk_combo_box_text_buildable_interface_init (GtkBuildableIface *iface); |
105 | static gboolean gtk_combo_box_text_buildable_custom_tag_start (GtkBuildable *buildable, |
106 | GtkBuilder *builder, |
107 | GObject *child, |
108 | const char *tagname, |
109 | GtkBuildableParser *parser, |
110 | gpointer *data); |
111 | |
112 | static void gtk_combo_box_text_buildable_custom_finished (GtkBuildable *buildable, |
113 | GtkBuilder *builder, |
114 | GObject *child, |
115 | const char *tagname, |
116 | gpointer user_data); |
117 | |
118 | |
119 | static GtkBuildableIface *buildable_parent_iface = NULL; |
120 | |
121 | G_DEFINE_TYPE_WITH_CODE (GtkComboBoxText, gtk_combo_box_text, GTK_TYPE_COMBO_BOX, |
122 | G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, |
123 | gtk_combo_box_text_buildable_interface_init)); |
124 | |
125 | static void |
126 | gtk_combo_box_text_constructed (GObject *object) |
127 | { |
128 | const int text_column = 0; |
129 | |
130 | G_OBJECT_CLASS (gtk_combo_box_text_parent_class)->constructed (object); |
131 | |
132 | gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (object), text_column); |
133 | gtk_combo_box_set_id_column (GTK_COMBO_BOX (object), id_column: 1); |
134 | |
135 | if (!gtk_combo_box_get_has_entry (GTK_COMBO_BOX (object))) |
136 | { |
137 | GtkCellRenderer *cell; |
138 | |
139 | cell = gtk_cell_renderer_text_new (); |
140 | gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (object), cell, TRUE); |
141 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (object), cell, |
142 | "text" , text_column, |
143 | NULL); |
144 | } |
145 | } |
146 | |
147 | static void |
148 | gtk_combo_box_text_init (GtkComboBoxText *combo_box) |
149 | { |
150 | GtkListStore *store; |
151 | |
152 | store = gtk_list_store_new (n_columns: 2, G_TYPE_STRING, G_TYPE_STRING); |
153 | gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store)); |
154 | g_object_unref (object: store); |
155 | } |
156 | |
157 | static void |
158 | gtk_combo_box_text_class_init (GtkComboBoxTextClass *klass) |
159 | { |
160 | GObjectClass *object_class; |
161 | |
162 | object_class = (GObjectClass *)klass; |
163 | object_class->constructed = gtk_combo_box_text_constructed; |
164 | } |
165 | |
166 | static void |
167 | gtk_combo_box_text_buildable_interface_init (GtkBuildableIface *iface) |
168 | { |
169 | buildable_parent_iface = g_type_interface_peek_parent (g_iface: iface); |
170 | |
171 | iface->custom_tag_start = gtk_combo_box_text_buildable_custom_tag_start; |
172 | iface->custom_finished = gtk_combo_box_text_buildable_custom_finished; |
173 | } |
174 | |
175 | typedef struct { |
176 | GtkBuilder *builder; |
177 | GObject *object; |
178 | const char *domain; |
179 | char *id; |
180 | |
181 | GString *string; |
182 | |
183 | char *context; |
184 | guint translatable : 1; |
185 | |
186 | guint is_text : 1; |
187 | } ItemParserData; |
188 | |
189 | static void |
190 | item_start_element (GtkBuildableParseContext *context, |
191 | const char *element_name, |
192 | const char **names, |
193 | const char **values, |
194 | gpointer user_data, |
195 | GError **error) |
196 | { |
197 | ItemParserData *data = (ItemParserData*)user_data; |
198 | |
199 | if (strcmp (s1: element_name, s2: "items" ) == 0) |
200 | { |
201 | if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object" , error)) |
202 | return; |
203 | |
204 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
205 | first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL, |
206 | G_MARKUP_COLLECT_INVALID)) |
207 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
208 | } |
209 | else if (strcmp (s1: element_name, s2: "item" ) == 0) |
210 | { |
211 | const char *id = NULL; |
212 | gboolean translatable = FALSE; |
213 | const char *msg_context = NULL; |
214 | |
215 | if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "items" , error)) |
216 | return; |
217 | |
218 | if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error, |
219 | first_type: G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, first_attr: "id" , &id, |
220 | G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable" , &translatable, |
221 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments" , NULL, |
222 | G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context" , &msg_context, |
223 | G_MARKUP_COLLECT_INVALID)) |
224 | { |
225 | _gtk_builder_prefix_error (builder: data->builder, context, error); |
226 | return; |
227 | } |
228 | |
229 | data->is_text = TRUE; |
230 | data->translatable = translatable; |
231 | data->context = g_strdup (str: msg_context); |
232 | data->id = g_strdup (str: id); |
233 | } |
234 | else |
235 | { |
236 | _gtk_builder_error_unhandled_tag (builder: data->builder, context, |
237 | object: "GtkComboBoxText" , element_name, |
238 | error); |
239 | } |
240 | } |
241 | |
242 | static void |
243 | item_text (GtkBuildableParseContext *context, |
244 | const char *text, |
245 | gsize text_len, |
246 | gpointer user_data, |
247 | GError **error) |
248 | { |
249 | ItemParserData *data = (ItemParserData*)user_data; |
250 | |
251 | if (data->is_text) |
252 | g_string_append_len (string: data->string, val: text, len: text_len); |
253 | } |
254 | |
255 | static void |
256 | item_end_element (GtkBuildableParseContext *context, |
257 | const char *element_name, |
258 | gpointer user_data, |
259 | GError **error) |
260 | { |
261 | ItemParserData *data = (ItemParserData*)user_data; |
262 | |
263 | /* Append the translated strings */ |
264 | if (data->string->len) |
265 | { |
266 | if (data->translatable) |
267 | { |
268 | const char *translated; |
269 | |
270 | translated = _gtk_builder_parser_translate (domain: data->domain, |
271 | context: data->context, |
272 | text: data->string->str); |
273 | g_string_assign (string: data->string, rval: translated); |
274 | } |
275 | |
276 | gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (data->object), id: data->id, text: data->string->str); |
277 | } |
278 | |
279 | data->translatable = FALSE; |
280 | g_string_set_size (string: data->string, len: 0); |
281 | g_clear_pointer (&data->context, g_free); |
282 | g_clear_pointer (&data->id, g_free); |
283 | data->is_text = FALSE; |
284 | } |
285 | |
286 | static const GtkBuildableParser item_parser = |
287 | { |
288 | item_start_element, |
289 | item_end_element, |
290 | item_text |
291 | }; |
292 | |
293 | static gboolean |
294 | gtk_combo_box_text_buildable_custom_tag_start (GtkBuildable *buildable, |
295 | GtkBuilder *builder, |
296 | GObject *child, |
297 | const char *tagname, |
298 | GtkBuildableParser *parser, |
299 | gpointer *parser_data) |
300 | { |
301 | if (buildable_parent_iface->custom_tag_start (buildable, builder, child, |
302 | tagname, parser, parser_data)) |
303 | return TRUE; |
304 | |
305 | if (strcmp (s1: tagname, s2: "items" ) == 0) |
306 | { |
307 | ItemParserData *data; |
308 | |
309 | data = g_slice_new0 (ItemParserData); |
310 | data->builder = g_object_ref (builder); |
311 | data->object = (GObject *) g_object_ref (buildable); |
312 | data->domain = gtk_builder_get_translation_domain (builder); |
313 | data->string = g_string_new (init: "" ); |
314 | |
315 | *parser = item_parser; |
316 | *parser_data = data; |
317 | |
318 | return TRUE; |
319 | } |
320 | |
321 | return FALSE; |
322 | } |
323 | |
324 | static void |
325 | gtk_combo_box_text_buildable_custom_finished (GtkBuildable *buildable, |
326 | GtkBuilder *builder, |
327 | GObject *child, |
328 | const char *tagname, |
329 | gpointer user_data) |
330 | { |
331 | ItemParserData *data; |
332 | |
333 | buildable_parent_iface->custom_finished (buildable, builder, child, |
334 | tagname, user_data); |
335 | |
336 | if (strcmp (s1: tagname, s2: "items" ) == 0) |
337 | { |
338 | data = (ItemParserData*)user_data; |
339 | |
340 | g_object_unref (object: data->object); |
341 | g_object_unref (object: data->builder); |
342 | g_string_free (string: data->string, TRUE); |
343 | g_slice_free (ItemParserData, data); |
344 | } |
345 | } |
346 | |
347 | /** |
348 | * gtk_combo_box_text_new: |
349 | * |
350 | * Creates a new `GtkComboBoxText`. |
351 | * |
352 | * Returns: A new `GtkComboBoxText` |
353 | */ |
354 | GtkWidget * |
355 | gtk_combo_box_text_new (void) |
356 | { |
357 | return g_object_new (GTK_TYPE_COMBO_BOX_TEXT, |
358 | NULL); |
359 | } |
360 | |
361 | /** |
362 | * gtk_combo_box_text_new_with_entry: |
363 | * |
364 | * Creates a new `GtkComboBoxText` with an entry. |
365 | * |
366 | * Returns: a new `GtkComboBoxText` |
367 | */ |
368 | GtkWidget * |
369 | gtk_combo_box_text_new_with_entry (void) |
370 | { |
371 | return g_object_new (GTK_TYPE_COMBO_BOX_TEXT, |
372 | first_property_name: "has-entry" , TRUE, |
373 | NULL); |
374 | } |
375 | |
376 | /** |
377 | * gtk_combo_box_text_append_text: |
378 | * @combo_box: A `GtkComboBoxText` |
379 | * @text: A string |
380 | * |
381 | * Appends @text to the list of strings stored in @combo_box. |
382 | * |
383 | * This is the same as calling [method@Gtk.ComboBoxText.insert_text] |
384 | * with a position of -1. |
385 | */ |
386 | void |
387 | gtk_combo_box_text_append_text (GtkComboBoxText *combo_box, |
388 | const char *text) |
389 | { |
390 | gtk_combo_box_text_insert (combo_box, position: -1, NULL, text); |
391 | } |
392 | |
393 | /** |
394 | * gtk_combo_box_text_prepend_text: |
395 | * @combo_box: A `GtkComboBox` |
396 | * @text: A string |
397 | * |
398 | * Prepends @text to the list of strings stored in @combo_box. |
399 | * |
400 | * This is the same as calling [method@Gtk.ComboBoxText.insert_text] |
401 | * with a position of 0. |
402 | */ |
403 | void |
404 | gtk_combo_box_text_prepend_text (GtkComboBoxText *combo_box, |
405 | const char *text) |
406 | { |
407 | gtk_combo_box_text_insert (combo_box, position: 0, NULL, text); |
408 | } |
409 | |
410 | /** |
411 | * gtk_combo_box_text_insert_text: |
412 | * @combo_box: A `GtkComboBoxText` |
413 | * @position: An index to insert @text |
414 | * @text: A string |
415 | * |
416 | * Inserts @text at @position in the list of strings stored in @combo_box. |
417 | * |
418 | * If @position is negative then @text is appended. |
419 | * |
420 | * This is the same as calling [method@Gtk.ComboBoxText.insert] |
421 | * with a %NULL ID string. |
422 | */ |
423 | void |
424 | gtk_combo_box_text_insert_text (GtkComboBoxText *combo_box, |
425 | int position, |
426 | const char *text) |
427 | { |
428 | gtk_combo_box_text_insert (combo_box, position, NULL, text); |
429 | } |
430 | |
431 | /** |
432 | * gtk_combo_box_text_append: |
433 | * @combo_box: A `GtkComboBoxText` |
434 | * @id: (nullable): a string ID for this value |
435 | * @text: A string |
436 | * |
437 | * Appends @text to the list of strings stored in @combo_box. |
438 | * |
439 | * If @id is non-%NULL then it is used as the ID of the row. |
440 | * |
441 | * This is the same as calling [method@Gtk.ComboBoxText.insert] |
442 | * with a position of -1. |
443 | */ |
444 | void |
445 | gtk_combo_box_text_append (GtkComboBoxText *combo_box, |
446 | const char *id, |
447 | const char *text) |
448 | { |
449 | gtk_combo_box_text_insert (combo_box, position: -1, id, text); |
450 | } |
451 | |
452 | /** |
453 | * gtk_combo_box_text_prepend: |
454 | * @combo_box: A `GtkComboBox` |
455 | * @id: (nullable): a string ID for this value |
456 | * @text: a string |
457 | * |
458 | * Prepends @text to the list of strings stored in @combo_box. |
459 | * |
460 | * If @id is non-%NULL then it is used as the ID of the row. |
461 | * |
462 | * This is the same as calling [method@Gtk.ComboBoxText.insert] |
463 | * with a position of 0. |
464 | */ |
465 | void |
466 | gtk_combo_box_text_prepend (GtkComboBoxText *combo_box, |
467 | const char *id, |
468 | const char *text) |
469 | { |
470 | gtk_combo_box_text_insert (combo_box, position: 0, id, text); |
471 | } |
472 | |
473 | |
474 | /** |
475 | * gtk_combo_box_text_insert: |
476 | * @combo_box: A `GtkComboBoxText` |
477 | * @position: An index to insert @text |
478 | * @id: (nullable): a string ID for this value |
479 | * @text: A string to display |
480 | * |
481 | * Inserts @text at @position in the list of strings stored in @combo_box. |
482 | * |
483 | * If @id is non-%NULL then it is used as the ID of the row. |
484 | * See [property@Gtk.ComboBox:id-column]. |
485 | * |
486 | * If @position is negative then @text is appended. |
487 | */ |
488 | void |
489 | gtk_combo_box_text_insert (GtkComboBoxText *combo_box, |
490 | int position, |
491 | const char *id, |
492 | const char *text) |
493 | { |
494 | GtkListStore *store; |
495 | GtkTreeIter iter; |
496 | int text_column; |
497 | |
498 | g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box)); |
499 | g_return_if_fail (text != NULL); |
500 | |
501 | store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box))); |
502 | g_return_if_fail (GTK_IS_LIST_STORE (store)); |
503 | |
504 | text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box)); |
505 | |
506 | if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo_box))) |
507 | g_return_if_fail (text_column >= 0); |
508 | else if (text_column < 0) |
509 | text_column = 0; |
510 | |
511 | g_return_if_fail (gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), text_column) == G_TYPE_STRING); |
512 | |
513 | if (position < 0) |
514 | gtk_list_store_append (list_store: store, iter: &iter); |
515 | else |
516 | gtk_list_store_insert (list_store: store, iter: &iter, position); |
517 | |
518 | gtk_list_store_set (list_store: store, iter: &iter, text_column, text, -1); |
519 | |
520 | if (id != NULL) |
521 | { |
522 | int id_column; |
523 | |
524 | id_column = gtk_combo_box_get_id_column (GTK_COMBO_BOX (combo_box)); |
525 | g_return_if_fail (id_column >= 0); |
526 | g_return_if_fail (gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), id_column) == G_TYPE_STRING); |
527 | |
528 | gtk_list_store_set (list_store: store, iter: &iter, id_column, id, -1); |
529 | } |
530 | } |
531 | |
532 | /** |
533 | * gtk_combo_box_text_remove: |
534 | * @combo_box: A `GtkComboBox` |
535 | * @position: Index of the item to remove |
536 | * |
537 | * Removes the string at @position from @combo_box. |
538 | */ |
539 | void |
540 | gtk_combo_box_text_remove (GtkComboBoxText *combo_box, |
541 | int position) |
542 | { |
543 | GtkTreeModel *model; |
544 | GtkListStore *store; |
545 | GtkTreeIter iter; |
546 | |
547 | g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box)); |
548 | g_return_if_fail (position >= 0); |
549 | |
550 | model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); |
551 | store = GTK_LIST_STORE (model); |
552 | g_return_if_fail (GTK_IS_LIST_STORE (store)); |
553 | |
554 | if (gtk_tree_model_iter_nth_child (tree_model: model, iter: &iter, NULL, n: position)) |
555 | gtk_list_store_remove (list_store: store, iter: &iter); |
556 | } |
557 | |
558 | /** |
559 | * gtk_combo_box_text_remove_all: |
560 | * @combo_box: A `GtkComboBoxText` |
561 | * |
562 | * Removes all the text entries from the combo box. |
563 | */ |
564 | void |
565 | gtk_combo_box_text_remove_all (GtkComboBoxText *combo_box) |
566 | { |
567 | GtkListStore *store; |
568 | |
569 | g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box)); |
570 | |
571 | store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box))); |
572 | gtk_list_store_clear (list_store: store); |
573 | } |
574 | |
575 | /** |
576 | * gtk_combo_box_text_get_active_text: |
577 | * @combo_box: A `GtkComboBoxText` |
578 | * |
579 | * Returns the currently active string in @combo_box. |
580 | * |
581 | * If no row is currently selected, %NULL is returned. |
582 | * If @combo_box contains an entry, this function will |
583 | * return its contents (which will not necessarily |
584 | * be an item from the list). |
585 | * |
586 | * Returns: (nullable) (transfer full): a newly allocated |
587 | * string containing the currently active text. |
588 | * Must be freed with g_free(). |
589 | */ |
590 | char * |
591 | gtk_combo_box_text_get_active_text (GtkComboBoxText *combo_box) |
592 | { |
593 | GtkTreeIter iter; |
594 | char *text = NULL; |
595 | |
596 | g_return_val_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box), NULL); |
597 | |
598 | if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo_box))) |
599 | { |
600 | GtkWidget *entry; |
601 | |
602 | entry = gtk_combo_box_get_child (GTK_COMBO_BOX (combo_box)); |
603 | text = g_strdup (str: gtk_editable_get_text (GTK_EDITABLE (entry))); |
604 | } |
605 | else if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), iter: &iter)) |
606 | { |
607 | GtkTreeModel *model; |
608 | int text_column; |
609 | |
610 | model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); |
611 | g_return_val_if_fail (GTK_IS_LIST_STORE (model), NULL); |
612 | text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box)); |
613 | g_return_val_if_fail (text_column >= 0, NULL); |
614 | g_return_val_if_fail (gtk_tree_model_get_column_type (model, text_column) == G_TYPE_STRING, NULL); |
615 | gtk_tree_model_get (tree_model: model, iter: &iter, text_column, &text, -1); |
616 | } |
617 | |
618 | return text; |
619 | } |
620 | |