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 <string.h>
20 
21 #include "config.h"
22 
23 #include "gtkcomboboxtext.h"
24 #include "gtkcombobox.h"
25 #include "gtkcellrenderertext.h"
26 #include "gtkcelllayout.h"
27 #include "gtkbuildable.h"
28 #include "gtkbuilderprivate.h"
29 #include "gtkalias.h"
30 
31 /**
32  * SECTION:gtkcomboboxtext
33  * @Short_description: A simple, text-only combo box
34  * @Title: GtkComboBoxText
35  * @See_also: @GtkComboBox
36  *
37  * A GtkComboBoxText is a simple variant of #GtkComboBox that hides
38  * the model-view complexity for simple text-only use cases.
39  *
40  * To create a GtkComboBoxText, use gtk_combo_box_text_new() or
41  * gtk_combo_box_text_new_with_entry().
42  *
43  * You can add items to a GtkComboBoxText with
44  * gtk_combo_box_text_append_text(), gtk_combo_box_text_insert_text()
45  * or gtk_combo_box_text_prepend_text() and remove options with
46  * gtk_combo_box_text_remove().
47  *
48  * If the GtkComboBoxText contains an entry (via the 'has-entry' property),
49  * its contents can be retrieved using gtk_combo_box_text_get_active_text().
50  * The entry itself can be accessed by calling gtk_bin_get_child() on the
51  * combo box.
52  *
53  * <refsect2 id="GtkComboBoxText-BUILDER-UI">
54  * <title>GtkComboBoxText as GtkBuildable</title>
55  * <para>
56  * The GtkComboBoxText implementation of the GtkBuildable interface
57  * supports adding items directly using the &lt;items&gt element
58  * and specifying &lt;item&gt; elements for each item. Each &lt;item&gt;
59  * element supports the regular translation attributes "translatable",
60  * "context" and "comments".
61  * </para>
62  * <example>
63  * <title>A UI definition fragment specifying GtkComboBoxText items</title>
64  * <programlisting><![CDATA[
65  * <object class="GtkComboBoxText">
66  *   <items>
67  *     <item translatable="yes">Factory</item>
68  *     <item translatable="yes">Home</item>
69  *     <item translatable="yes">Subway</item>
70  *   </items>
71  * </object>
72  * ]]></programlisting>
73  * </example>
74  * </refsect2>
75  */
76 
77 static void     gtk_combo_box_text_buildable_interface_init     (GtkBuildableIface *iface);
78 static gboolean gtk_combo_box_text_buildable_custom_tag_start   (GtkBuildable     *buildable,
79 								 GtkBuilder       *builder,
80 								 GObject          *child,
81 								 const gchar      *tagname,
82 								 GMarkupParser    *parser,
83 								 gpointer         *data);
84 
85 static void     gtk_combo_box_text_buildable_custom_finished    (GtkBuildable     *buildable,
86 								 GtkBuilder       *builder,
87 								 GObject          *child,
88 								 const gchar      *tagname,
89 								 gpointer          user_data);
90 
91 static GtkBuildableIface *buildable_parent_iface = NULL;
92 
93 G_DEFINE_TYPE_WITH_CODE (GtkComboBoxText, gtk_combo_box_text, GTK_TYPE_COMBO_BOX,
94 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
95 						gtk_combo_box_text_buildable_interface_init));
96 
97 static GObject *
gtk_combo_box_text_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)98 gtk_combo_box_text_constructor (GType                  type,
99                                 guint                  n_construct_properties,
100                                 GObjectConstructParam *construct_properties)
101 {
102   GObject            *object;
103 
104   object = G_OBJECT_CLASS (gtk_combo_box_text_parent_class)->constructor
105     (type, n_construct_properties, construct_properties);
106 
107   if (!gtk_combo_box_get_has_entry (GTK_COMBO_BOX (object)))
108     {
109       GtkCellRenderer *cell;
110 
111       cell = gtk_cell_renderer_text_new ();
112       gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (object), cell, TRUE);
113       gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (object), cell,
114                                       "text", 0,
115                                       NULL);
116     }
117 
118   return object;
119 }
120 
121 static void
gtk_combo_box_text_init(GtkComboBoxText * combo_box)122 gtk_combo_box_text_init (GtkComboBoxText *combo_box)
123 {
124   GtkListStore *store;
125 
126   store = gtk_list_store_new (1, G_TYPE_STRING);
127   gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
128   g_object_unref (store);
129 }
130 
131 static void
gtk_combo_box_text_class_init(GtkComboBoxTextClass * klass)132 gtk_combo_box_text_class_init (GtkComboBoxTextClass *klass)
133 {
134   GObjectClass *object_class;
135 
136   object_class = (GObjectClass *)klass;
137   object_class->constructor = gtk_combo_box_text_constructor;
138 }
139 
140 static void
gtk_combo_box_text_buildable_interface_init(GtkBuildableIface * iface)141 gtk_combo_box_text_buildable_interface_init (GtkBuildableIface *iface)
142 {
143   buildable_parent_iface = g_type_interface_peek_parent (iface);
144 
145   iface->custom_tag_start = gtk_combo_box_text_buildable_custom_tag_start;
146   iface->custom_finished = gtk_combo_box_text_buildable_custom_finished;
147 }
148 
149 typedef struct {
150   GtkBuilder    *builder;
151   GObject       *object;
152   const gchar   *domain;
153 
154   gchar         *context;
155   gchar         *string;
156   guint          translatable : 1;
157 
158   guint          is_text : 1;
159 } ItemParserData;
160 
161 static void
item_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** names,const gchar ** values,gpointer user_data,GError ** error)162 item_start_element (GMarkupParseContext *context,
163 		    const gchar         *element_name,
164 		    const gchar        **names,
165 		    const gchar        **values,
166 		    gpointer             user_data,
167 		    GError             **error)
168 {
169   ItemParserData *data = (ItemParserData*)user_data;
170   guint i;
171 
172   if (strcmp (element_name, "item") == 0)
173     {
174       data->is_text = TRUE;
175 
176       for (i = 0; names[i]; i++)
177 	{
178 	  if (strcmp (names[i], "translatable") == 0)
179 	    {
180 	      gboolean bval;
181 
182 	      if (!_gtk_builder_boolean_from_string (values[i], &bval,
183 						     error))
184 		return;
185 
186 	      data->translatable = bval;
187 	    }
188 	  else if (strcmp (names[i], "comments") == 0)
189 	    {
190 	      /* do nothing, comments are for translators */
191 	    }
192 	  else if (strcmp (names[i], "context") == 0)
193 	    data->context = g_strdup (values[i]);
194 	  else
195 	    g_warning ("Unknown custom combo box item attribute: %s", names[i]);
196 	}
197     }
198 }
199 
200 static void
item_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)201 item_text (GMarkupParseContext *context,
202 	   const gchar         *text,
203 	   gsize                text_len,
204 	   gpointer             user_data,
205 	   GError             **error)
206 {
207   ItemParserData *data = (ItemParserData*)user_data;
208   gchar *string;
209 
210   if (!data->is_text)
211     return;
212 
213   string = g_strndup (text, text_len);
214 
215   if (data->translatable && text_len)
216     {
217       gchar *translated;
218 
219       /* FIXME: This will not use the domain set in the .ui file,
220        * since the parser is not telling the builder about the domain.
221        * However, it will work for gtk_builder_set_translation_domain() calls.
222        */
223       translated = _gtk_builder_parser_translate (data->domain,
224 						  data->context,
225 						  string);
226       g_free (string);
227       string = translated;
228     }
229 
230   data->string = string;
231 }
232 
233 static void
item_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)234 item_end_element (GMarkupParseContext *context,
235 		  const gchar         *element_name,
236 		  gpointer             user_data,
237 		  GError             **error)
238 {
239   ItemParserData *data = (ItemParserData*)user_data;
240 
241   /* Append the translated strings */
242   if (data->string)
243     gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (data->object), data->string);
244 
245   data->translatable = FALSE;
246   g_free (data->context);
247   g_free (data->string);
248   data->context = NULL;
249   data->string = NULL;
250   data->is_text = FALSE;
251 }
252 
253 static const GMarkupParser item_parser =
254   {
255     item_start_element,
256     item_end_element,
257     item_text
258   };
259 
260 static gboolean
gtk_combo_box_text_buildable_custom_tag_start(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,GMarkupParser * parser,gpointer * data)261 gtk_combo_box_text_buildable_custom_tag_start (GtkBuildable     *buildable,
262 					       GtkBuilder       *builder,
263 					       GObject          *child,
264 					       const gchar      *tagname,
265 					       GMarkupParser    *parser,
266 					       gpointer         *data)
267 {
268   if (buildable_parent_iface->custom_tag_start (buildable, builder, child,
269 						tagname, parser, data))
270     return TRUE;
271 
272   if (strcmp (tagname, "items") == 0)
273     {
274       ItemParserData *parser_data;
275 
276       parser_data = g_slice_new0 (ItemParserData);
277       parser_data->builder = g_object_ref (builder);
278       parser_data->object = g_object_ref (buildable);
279       parser_data->domain = gtk_builder_get_translation_domain (builder);
280       *parser = item_parser;
281       *data = parser_data;
282       return TRUE;
283     }
284   return FALSE;
285 }
286 
287 static void
gtk_combo_box_text_buildable_custom_finished(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * tagname,gpointer user_data)288 gtk_combo_box_text_buildable_custom_finished (GtkBuildable *buildable,
289 					      GtkBuilder   *builder,
290 					      GObject      *child,
291 					      const gchar  *tagname,
292 					      gpointer      user_data)
293 {
294   ItemParserData *data;
295 
296   buildable_parent_iface->custom_finished (buildable, builder, child,
297 					   tagname, user_data);
298 
299   if (strcmp (tagname, "items") == 0)
300     {
301       data = (ItemParserData*)user_data;
302 
303       g_object_unref (data->object);
304       g_object_unref (data->builder);
305       g_slice_free (ItemParserData, data);
306     }
307 }
308 
309 /**
310  * gtk_combo_box_text_new:
311  *
312  * Creates a new #GtkComboBoxText, which is a #GtkComboBox just displaying
313  * strings. See gtk_combo_box_entry_new_with_text().
314  *
315  * Return value: A new #GtkComboBoxText
316  *
317  * Since: 2.24
318  */
319 GtkWidget *
gtk_combo_box_text_new(void)320 gtk_combo_box_text_new (void)
321 {
322   return g_object_new (GTK_TYPE_COMBO_BOX_TEXT,
323                        "entry-text-column", 0,
324                        NULL);
325 }
326 
327 /**
328  * gtk_combo_box_text_new_with_entry:
329  *
330  * Creates a new #GtkComboBoxText, which is a #GtkComboBox just displaying
331  * strings. The combo box created by this function has an entry.
332  *
333  * Return value: a new #GtkComboBoxText
334  *
335  * Since: 2.24
336  */
337 GtkWidget *
gtk_combo_box_text_new_with_entry(void)338 gtk_combo_box_text_new_with_entry (void)
339 {
340   return g_object_new (GTK_TYPE_COMBO_BOX_TEXT,
341                        "has-entry", TRUE,
342                        "entry-text-column", 0,
343                        NULL);
344 }
345 
346 /**
347  * gtk_combo_box_text_append_text:
348  * @combo_box: A #GtkComboBoxText
349  * @text: A string
350  *
351  * Appends @string to the list of strings stored in @combo_box.
352  *
353  * Since: 2.24
354  */
355 void
gtk_combo_box_text_append_text(GtkComboBoxText * combo_box,const gchar * text)356 gtk_combo_box_text_append_text (GtkComboBoxText *combo_box,
357                                 const gchar     *text)
358 {
359   GtkListStore *store;
360   GtkTreeIter iter;
361   gint text_column;
362   gint column_type;
363 
364   g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
365   g_return_if_fail (text != NULL);
366 
367   store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
368   g_return_if_fail (GTK_IS_LIST_STORE (store));
369 
370   text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
371   if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo_box)))
372     g_return_if_fail (text_column >= 0);
373   else if (text_column < 0)
374     text_column = 0;
375 
376   column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), text_column);
377   g_return_if_fail (column_type == G_TYPE_STRING);
378 
379   gtk_list_store_append (store, &iter);
380   gtk_list_store_set (store, &iter, text_column, text, -1);
381 }
382 
383 /**
384  * gtk_combo_box_text_insert_text:
385  * @combo_box: A #GtkComboBoxText
386  * @position: An index to insert @text
387  * @text: A string
388  *
389  * Inserts @string at @position in the list of strings stored in @combo_box.
390  *
391  * Since: 2.24
392  */
393 void
gtk_combo_box_text_insert_text(GtkComboBoxText * combo_box,gint position,const gchar * text)394 gtk_combo_box_text_insert_text (GtkComboBoxText *combo_box,
395                                 gint             position,
396                                 const gchar     *text)
397 {
398   GtkListStore *store;
399   GtkTreeIter iter;
400   gint text_column;
401   gint column_type;
402 
403   g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
404   g_return_if_fail (position >= 0);
405   g_return_if_fail (text != NULL);
406 
407   store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
408   g_return_if_fail (GTK_IS_LIST_STORE (store));
409   text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
410   column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), text_column);
411   g_return_if_fail (column_type == G_TYPE_STRING);
412 
413   gtk_list_store_insert (store, &iter, position);
414   gtk_list_store_set (store, &iter, text_column, text, -1);
415 }
416 
417 /**
418  * gtk_combo_box_text_prepend_text:
419  * @combo_box: A #GtkComboBox
420  * @text: A string
421  *
422  * Prepends @string to the list of strings stored in @combo_box.
423  *
424  * Since: 2.24
425  */
426 void
gtk_combo_box_text_prepend_text(GtkComboBoxText * combo_box,const gchar * text)427 gtk_combo_box_text_prepend_text (GtkComboBoxText *combo_box,
428                                  const gchar     *text)
429 {
430   GtkListStore *store;
431   GtkTreeIter iter;
432   gint text_column;
433   gint column_type;
434 
435   g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
436   g_return_if_fail (text != NULL);
437 
438   store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
439   g_return_if_fail (GTK_IS_LIST_STORE (store));
440 
441   text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
442   column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), text_column);
443   g_return_if_fail (column_type == G_TYPE_STRING);
444 
445   gtk_list_store_prepend (store, &iter);
446   gtk_list_store_set (store, &iter, text_column, text, -1);
447 }
448 
449 /**
450  * gtk_combo_box_text_remove:
451  * @combo_box: A #GtkComboBox
452  * @position: Index of the item to remove
453  *
454  * Removes the string at @position from @combo_box.
455  *
456  * Since: 2.24
457  */
458 void
gtk_combo_box_text_remove(GtkComboBoxText * combo_box,gint position)459 gtk_combo_box_text_remove (GtkComboBoxText *combo_box,
460                            gint             position)
461 {
462   GtkTreeModel *model;
463   GtkListStore *store;
464   GtkTreeIter iter;
465 
466   g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
467   g_return_if_fail (position >= 0);
468 
469   model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
470   store = GTK_LIST_STORE (model);
471   g_return_if_fail (GTK_IS_LIST_STORE (store));
472 
473   if (gtk_tree_model_iter_nth_child (model, &iter, NULL, position))
474     gtk_list_store_remove (store, &iter);
475 }
476 
477 /**
478  * gtk_combo_box_text_get_active_text:
479  * @combo_box: A #GtkComboBoxText
480  *
481  * Returns the currently active string in @combo_box, or %NULL
482  * if none is selected. If @combo_box contains an entry, this
483  * function will return its contents (which will not necessarily
484  * be an item from the list).
485  *
486  * Returns: a newly allocated string containing the currently
487  *     active text. Must be freed with g_free().
488  *
489  * Since: 2.24
490  */
491 gchar *
gtk_combo_box_text_get_active_text(GtkComboBoxText * combo_box)492 gtk_combo_box_text_get_active_text (GtkComboBoxText *combo_box)
493 {
494   GtkTreeIter iter;
495   gchar *text = NULL;
496 
497   g_return_val_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box), NULL);
498 
499  if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo_box)))
500    {
501      GtkWidget *entry;
502 
503      entry = gtk_bin_get_child (GTK_BIN (combo_box));
504      text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
505    }
506   else if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
507     {
508       GtkTreeModel *model;
509       gint text_column;
510       gint column_type;
511 
512       model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
513       g_return_val_if_fail (GTK_IS_LIST_STORE (model), NULL);
514       text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
515       column_type = gtk_tree_model_get_column_type (model, text_column);
516       g_return_val_if_fail (column_type == G_TYPE_STRING, NULL);
517       gtk_tree_model_get (model, &iter, text_column, &text, -1);
518     }
519 
520   return text;
521 }
522 
523 #define __GTK_COMBO_BOX_TEXT_C__
524 #include "gtkaliasdef.c"
525