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 * ![An example GtkComboBoxText](combo-box-text.png)
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
91typedef struct _GtkComboBoxTextClass GtkComboBoxTextClass;
92
93struct _GtkComboBoxText
94{
95 GtkComboBox parent_instance;
96};
97
98struct _GtkComboBoxTextClass
99{
100 GtkComboBoxClass parent_class;
101};
102
103
104static void gtk_combo_box_text_buildable_interface_init (GtkBuildableIface *iface);
105static 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
112static 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
119static GtkBuildableIface *buildable_parent_iface = NULL;
120
121G_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
125static void
126gtk_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
147static void
148gtk_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
157static void
158gtk_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
166static void
167gtk_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
175typedef 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
189static void
190item_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
242static void
243item_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
255static void
256item_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
286static const GtkBuildableParser item_parser =
287 {
288 item_start_element,
289 item_end_element,
290 item_text
291 };
292
293static gboolean
294gtk_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
324static void
325gtk_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 */
354GtkWidget *
355gtk_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 */
368GtkWidget *
369gtk_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 */
386void
387gtk_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 */
403void
404gtk_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 */
423void
424gtk_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 */
444void
445gtk_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 */
465void
466gtk_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 */
488void
489gtk_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 */
539void
540gtk_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 */
564void
565gtk_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 */
590char *
591gtk_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

source code of gtk/gtk/gtkcomboboxtext.c