gtkcomboboxtext.c 17 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/* GTK - The GIMP Toolkit
 *
 * Copyright (C) 2010 Christian Dywan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "gtkcomboboxtext.h"
#include "gtkcombobox.h"
#include "gtkcellrenderertext.h"
#include "gtkcelllayout.h"
25 26 27 28
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"

#include <string.h>
29

30 31 32 33
/**
 * SECTION:gtkcomboboxtext
 * @Short_description: A simple, text-only combo box
 * @Title: GtkComboBoxText
34
 * @See_also: #GtkComboBox
35 36 37 38 39 40 41 42 43 44
 *
 * A GtkComboBoxText is a simple variant of #GtkComboBox that hides
 * the model-view complexity for simple text-only use cases.
 *
 * To create a GtkComboBoxText, use gtk_combo_box_text_new() or
 * gtk_combo_box_text_new_with_entry().
 *
 * You can add items to a GtkComboBoxText with
 * gtk_combo_box_text_append_text(), gtk_combo_box_text_insert_text()
 * or gtk_combo_box_text_prepend_text() and remove options with
45
 * gtk_combo_box_text_remove().
46 47 48 49 50
 *
 * If the GtkComboBoxText contains an entry (via the 'has-entry' property),
 * its contents can be retrieved using gtk_combo_box_text_get_active_text().
 * The entry itself can be accessed by calling gtk_bin_get_child() on the
 * combo box.
51
 *
52 53 54
 * You should not call gtk_combo_box_set_model() or attempt to pack more cells
 * into this combo box via its GtkCellLayout interface.
 *
55 56 57 58
 * <refsect2 id="GtkComboBoxText-BUILDER-UI">
 * <title>GtkComboBoxText as GtkBuildable</title>
 * <para>
 * The GtkComboBoxText implementation of the GtkBuildable interface
59 60
 * supports adding items directly using the &lt;items&gt; element
 * and specifying &lt;item&gt; elements for each item. Each &lt;item&gt;
61 62
 * element can specify the "id" corresponding to the appended text and
 * also supports the regular translation attributes "translatable",
63 64 65 66 67 68 69
 * "context" and "comments".
 *
 * <example>
 * <title>A UI definition fragment specifying GtkComboBoxText items</title>
 * <programlisting><![CDATA[
 * <object class="GtkComboBoxText">
 *   <items>
70 71 72
 *     <item translatable="yes" id="factory">Factory</item>
 *     <item translatable="yes" id="home">Home</item>
 *     <item translatable="yes" id="subway">Subway</item>
73 74 75 76
 *   </items>
 * </object>
 * ]]></programlisting>
 * </example>
Matthias Clasen's avatar
Matthias Clasen committed
77 78
 * </para>
 * </refsect2>
79 80
 */

81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
static void     gtk_combo_box_text_buildable_interface_init     (GtkBuildableIface *iface);
static gboolean gtk_combo_box_text_buildable_custom_tag_start   (GtkBuildable     *buildable,
								 GtkBuilder       *builder,
								 GObject          *child,
								 const gchar      *tagname,
								 GMarkupParser    *parser,
								 gpointer         *data);

static void     gtk_combo_box_text_buildable_custom_finished    (GtkBuildable     *buildable,
								 GtkBuilder       *builder,
								 GObject          *child,
								 const gchar      *tagname,
								 gpointer          user_data);

static GtkBuildableIface *buildable_parent_iface = NULL;

G_DEFINE_TYPE_WITH_CODE (GtkComboBoxText, gtk_combo_box_text, GTK_TYPE_COMBO_BOX,
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
						gtk_combo_box_text_buildable_interface_init));
100

101 102 103 104
static GObject *
gtk_combo_box_text_constructor (GType                  type,
                                guint                  n_construct_properties,
                                GObjectConstructParam *construct_properties)
105
{
106 107
  GObject    *object;
  const gint text_column = 0;
108 109 110 111

  object = G_OBJECT_CLASS (gtk_combo_box_text_parent_class)->constructor
    (type, n_construct_properties, construct_properties);

112 113 114
  gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (object), text_column);
  gtk_combo_box_set_id_column (GTK_COMBO_BOX (object), 1);

115 116 117 118 119 120 121
  if (!gtk_combo_box_get_has_entry (GTK_COMBO_BOX (object)))
    {
      GtkCellRenderer *cell;

      cell = gtk_cell_renderer_text_new ();
      gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (object), cell, TRUE);
      gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (object), cell,
122
                                      "text", text_column,
123 124 125 126
                                      NULL);
    }

  return object;
127 128 129 130 131 132 133
}

static void
gtk_combo_box_text_init (GtkComboBoxText *combo_box)
{
  GtkListStore *store;

134
  store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
135 136
  gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
  g_object_unref (store);
137
}
138

139 140 141 142 143 144 145
static void
gtk_combo_box_text_class_init (GtkComboBoxTextClass *klass)
{
  GObjectClass *object_class;

  object_class = (GObjectClass *)klass;
  object_class->constructor = gtk_combo_box_text_constructor;
146 147
}

148 149 150 151 152 153 154 155 156 157 158 159 160
static void
gtk_combo_box_text_buildable_interface_init (GtkBuildableIface *iface)
{
  buildable_parent_iface = g_type_interface_peek_parent (iface);

  iface->custom_tag_start = gtk_combo_box_text_buildable_custom_tag_start;
  iface->custom_finished = gtk_combo_box_text_buildable_custom_finished;
}

typedef struct {
  GtkBuilder    *builder;
  GObject       *object;
  const gchar   *domain;
161
  gchar         *id;
162

163 164
  GString       *string;

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
  gchar         *context;
  guint          translatable : 1;

  guint          is_text : 1;
} ItemParserData;

static void
item_start_element (GMarkupParseContext *context,
		    const gchar         *element_name,
		    const gchar        **names,
		    const gchar        **values,
		    gpointer             user_data,
		    GError             **error)
{
  ItemParserData *data = (ItemParserData*)user_data;
  guint i;

  if (strcmp (element_name, "item") == 0)
    {
      data->is_text = TRUE;

      for (i = 0; names[i]; i++)
	{
	  if (strcmp (names[i], "translatable") == 0)
	    {
	      gboolean bval;

	      if (!_gtk_builder_boolean_from_string (values[i], &bval,
						     error))
		return;

	      data->translatable = bval;
	    }
	  else if (strcmp (names[i], "comments") == 0)
	    {
	      /* do nothing, comments are for translators */
	    }
	  else if (strcmp (names[i], "context") == 0) 
	    data->context = g_strdup (values[i]);
204 205
	  else if (strcmp (names[i], "id") == 0)
	    data->id = g_strdup (values[i]);
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
	  else
	    g_warning ("Unknown custom combo box item attribute: %s", names[i]);
	}
    }
}

static void
item_text (GMarkupParseContext *context,
	   const gchar         *text,
	   gsize                text_len,
	   gpointer             user_data,
	   GError             **error)
{
  ItemParserData *data = (ItemParserData*)user_data;

221 222
  if (data->is_text)
    g_string_append_len (data->string, text, text_len);
223 224 225 226 227 228 229 230 231 232 233
}

static void
item_end_element (GMarkupParseContext *context,
		  const gchar         *element_name,
		  gpointer             user_data,
		  GError             **error)
{
  ItemParserData *data = (ItemParserData*)user_data;

  /* Append the translated strings */
234 235 236 237
  if (data->string->len)
    {
      if (data->translatable)
	{
238
	  const gchar *translated;
239 240 241 242

	  translated = _gtk_builder_parser_translate (data->domain,
						      data->context,
						      data->string->str);
243
	  g_string_assign (data->string, translated);
244 245
	}

246
      gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (data->object), data->id, data->string->str);
247
    }
248 249

  data->translatable = FALSE;
250
  g_string_set_size (data->string, 0);
251 252
  g_free (data->context);
  data->context = NULL;
253 254
  g_free (data->id);
  data->id = NULL;
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
  data->is_text = FALSE;
}

static const GMarkupParser item_parser =
  {
    item_start_element,
    item_end_element,
    item_text
  };

static gboolean
gtk_combo_box_text_buildable_custom_tag_start (GtkBuildable     *buildable,
					       GtkBuilder       *builder,
					       GObject          *child,
					       const gchar      *tagname,
					       GMarkupParser    *parser,
					       gpointer         *data)
{
  if (buildable_parent_iface->custom_tag_start (buildable, builder, child, 
						tagname, parser, data))
    return TRUE;

  if (strcmp (tagname, "items") == 0)
    {
      ItemParserData *parser_data;

      parser_data = g_slice_new0 (ItemParserData);
      parser_data->builder = g_object_ref (builder);
      parser_data->object = g_object_ref (buildable);
      parser_data->domain = gtk_builder_get_translation_domain (builder);
285
      parser_data->string = g_string_new ("");
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
      *parser = item_parser;
      *data = parser_data;
      return TRUE;
    }
  return FALSE;
}

static void
gtk_combo_box_text_buildable_custom_finished (GtkBuildable *buildable,
					      GtkBuilder   *builder,
					      GObject      *child,
					      const gchar  *tagname,
					      gpointer      user_data)
{
  ItemParserData *data;

  buildable_parent_iface->custom_finished (buildable, builder, child, 
					   tagname, user_data);

  if (strcmp (tagname, "items") == 0)
    {
      data = (ItemParserData*)user_data;

      g_object_unref (data->object);
      g_object_unref (data->builder);
311
      g_string_free (data->string, TRUE);
312 313 314
      g_slice_free (ItemParserData, data);
    }
}
315

316 317 318 319
/**
 * gtk_combo_box_text_new:
 *
 * Creates a new #GtkComboBoxText, which is a #GtkComboBox just displaying
320
 * strings.
321 322 323 324 325 326 327 328
 *
 * Return value: A new #GtkComboBoxText
 *
 * Since: 2.24
 */
GtkWidget *
gtk_combo_box_text_new (void)
{
329 330
  return g_object_new (GTK_TYPE_COMBO_BOX_TEXT,
                       NULL);
331 332
}

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
/**
 * gtk_combo_box_text_new_with_entry:
 *
 * Creates a new #GtkComboBoxText, which is a #GtkComboBox just displaying
 * strings. The combo box created by this function has an entry.
 *
 * Return value: a new #GtkComboBoxText
 *
 * Since: 2.24
 */
GtkWidget *
gtk_combo_box_text_new_with_entry (void)
{
  return g_object_new (GTK_TYPE_COMBO_BOX_TEXT,
                       "has-entry", TRUE,
                       NULL);
}

351 352 353 354 355
/**
 * gtk_combo_box_text_append_text:
 * @combo_box: A #GtkComboBoxText
 * @text: A string
 *
356
 * Appends @text to the list of strings stored in @combo_box.
357
 *
358 359 360
 * This is the same as calling gtk_combo_box_text_insert_text() with a
 * position of -1.
 *
361 362 363 364 365 366
 * Since: 2.24
 */
void
gtk_combo_box_text_append_text (GtkComboBoxText *combo_box,
                                const gchar     *text)
{
367
  gtk_combo_box_text_insert (combo_box, -1, NULL, text);
368
}
369

370 371 372 373 374
/**
 * gtk_combo_box_text_prepend_text:
 * @combo_box: A #GtkComboBox
 * @text: A string
 *
375
 * Prepends @text to the list of strings stored in @combo_box.
376
 *
377 378 379
 * This is the same as calling gtk_combo_box_text_insert_text() with a
 * position of 0.
 *
380 381 382 383 384 385
 * Since: 2.24
 */
void
gtk_combo_box_text_prepend_text (GtkComboBoxText *combo_box,
                                 const gchar     *text)
{
386
  gtk_combo_box_text_insert (combo_box, 0, NULL, text);
387 388 389 390 391 392 393 394
}

/**
 * gtk_combo_box_text_insert_text:
 * @combo_box: A #GtkComboBoxText
 * @position: An index to insert @text
 * @text: A string
 *
395
 * Inserts @text at @position in the list of strings stored in @combo_box.
396
 *
397 398 399 400 401
 * If @position is negative then @text is appended.
 *
 * This is the same as calling gtk_combo_box_text_insert() with a %NULL
 * ID string.
 *
402 403 404 405 406 407
 * Since: 2.24
 */
void
gtk_combo_box_text_insert_text (GtkComboBoxText *combo_box,
                                gint             position,
                                const gchar     *text)
408 409 410 411 412 413 414
{
  gtk_combo_box_text_insert (combo_box, position, NULL, text);
}

/**
 * gtk_combo_box_text_append:
 * @combo_box: A #GtkComboBoxText
415
 * @id: (allow-none): a string ID for this value, or %NULL
416 417
 * @text: A string
 *
418 419
 * Appends @text to the list of strings stored in @combo_box.
 * If @id is non-%NULL then it is used as the ID of the row.
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
 *
 * This is the same as calling gtk_combo_box_text_insert() with a
 * position of -1.
 *
 * Since: 2.24
 */
void
gtk_combo_box_text_append (GtkComboBoxText *combo_box,
                           const gchar     *id,
                           const gchar     *text)
{
  gtk_combo_box_text_insert (combo_box, -1, id, text);
}

/**
 * gtk_combo_box_text_prepend:
 * @combo_box: A #GtkComboBox
Matthias Clasen's avatar
Matthias Clasen committed
437 438
 * @id: (allow-none): a string ID for this value, or %NULL
 * @text: a string
439
 *
440 441
 * Prepends @text to the list of strings stored in @combo_box.
 * If @id is non-%NULL then it is used as the ID of the row.
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
 *
 * This is the same as calling gtk_combo_box_text_insert() with a
 * position of 0.
 *
 * Since: 2.24
 */
void
gtk_combo_box_text_prepend (GtkComboBoxText *combo_box,
                            const gchar     *id,
                            const gchar     *text)
{
  gtk_combo_box_text_insert (combo_box, 0, id, text);
}


/**
 * gtk_combo_box_text_insert:
 * @combo_box: A #GtkComboBoxText
 * @position: An index to insert @text
Matthias Clasen's avatar
Matthias Clasen committed
461
 * @id: (allow-none): a string ID for this value, or %NULL
462 463 464 465
 * @text: A string to display
 *
 * Inserts @text at @position in the list of strings stored in @combo_box.
 * If @id is non-%NULL then it is used as the ID of the row.  See
466
 * #GtkComboBox:id-column.
467 468 469 470 471 472 473 474 475 476
 *
 * If @position is negative then @text is appended.
 *
 * Since: 3.0
 */
void
gtk_combo_box_text_insert (GtkComboBoxText *combo_box,
                           gint             position,
                           const gchar     *id,
                           const gchar     *text)
477 478 479
{
  GtkListStore *store;
  GtkTreeIter iter;
480
  gint text_column;
481 482 483 484 485 486 487
  gint column_type;

  g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
  g_return_if_fail (text != NULL);

  store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
  g_return_if_fail (GTK_IS_LIST_STORE (store));
488

489
  text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
490 491 492 493 494 495

  if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo_box)))
    g_return_if_fail (text_column >= 0);
  else if (text_column < 0)
    text_column = 0;

496
  column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), text_column);
497 498
  g_return_if_fail (column_type == G_TYPE_STRING);

499 500 501 502 503
  if (position < 0)
    gtk_list_store_append (store, &iter);
  else
    gtk_list_store_insert (store, &iter, position);

504
  gtk_list_store_set (store, &iter, text_column, text, -1);
505 506 507 508 509 510

  if (id != NULL)
    {
      gint id_column;

      id_column = gtk_combo_box_get_id_column (GTK_COMBO_BOX (combo_box));
511
      g_return_if_fail (id_column >= 0);
512 513 514 515 516
      column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), id_column);
      g_return_if_fail (column_type == G_TYPE_STRING);

      gtk_list_store_set (store, &iter, id_column, id, -1);
    }
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
}

/**
 * gtk_combo_box_text_remove:
 * @combo_box: A #GtkComboBox
 * @position: Index of the item to remove
 *
 * Removes the string at @position from @combo_box.
 *
 * Since: 2.24
 */
void
gtk_combo_box_text_remove (GtkComboBoxText *combo_box,
                           gint             position)
{
  GtkTreeModel *model;
  GtkListStore *store;
  GtkTreeIter iter;

  g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
  g_return_if_fail (position >= 0);

  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
  store = GTK_LIST_STORE (model);
  g_return_if_fail (GTK_IS_LIST_STORE (store));

  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, position))
    gtk_list_store_remove (store, &iter);
}

547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
/**
 * gtk_combo_box_text_remove_all:
 * @combo_box: A #GtkComboBoxText
 *
 * Removes all the text entries from the combo box.
 *
 * Since: 3.0
 */
void
gtk_combo_box_text_remove_all (GtkComboBoxText *combo_box)
{
  GtkListStore *store;

  g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));

  store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
  gtk_list_store_clear (store);
}

566 567 568 569
/**
 * gtk_combo_box_text_get_active_text:
 * @combo_box: A #GtkComboBoxText
 *
570 571 572 573
 * Returns the currently active string in @combo_box, or %NULL
 * if none is selected. If @combo_box contains an entry, this
 * function will return its contents (which will not necessarily
 * be an item from the list).
574
 *
575 576
 * Returns: (transfer full): a newly allocated string containing the
 *     currently active text. Must be freed with g_free().
577 578 579 580 581 582 583 584 585 586 587
 *
 * Since: 2.24
 */
gchar *
gtk_combo_box_text_get_active_text (GtkComboBoxText *combo_box)
{
  GtkTreeIter iter;
  gchar *text = NULL;

  g_return_val_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box), NULL);

588 589 590 591 592 593 594 595
 if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (combo_box)))
   {
     GtkWidget *entry;

     entry = gtk_bin_get_child (GTK_BIN (combo_box));
     text = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
   }
  else if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
596 597
    {
      GtkTreeModel *model;
598
      gint text_column;
599 600 601 602
      gint column_type;

      model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
      g_return_val_if_fail (GTK_IS_LIST_STORE (model), NULL);
603
      text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
604
      g_return_val_if_fail (text_column >= 0, NULL);
605
      column_type = gtk_tree_model_get_column_type (model, text_column);
606
      g_return_val_if_fail (column_type == G_TYPE_STRING, NULL);
607
      gtk_tree_model_get (model, &iter, text_column, &text, -1);
608 609 610 611
    }

  return text;
}