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