gtkcomboboxtext.c 18.4 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
 * If the GtkComboBoxText contains an entry (via the “has-entry” property),
48 49 50
 * 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
 * # GtkComboBoxText as GtkBuildable
56
 *
57 58 59 60 61
 * The GtkComboBoxText implementation of the GtkBuildable interface supports
 * adding items directly using the <items> element and specifying <item>
 * elements for each item. Each <item> element can specify the “id”
 * corresponding to the appended text and also supports the regular
 * translation attributes “translatable”, “context” and “comments”.
62
 *
63
 * Here is a UI definition fragment specifying GtkComboBoxText items:
64
 * |[
65 66
 * <object class="GtkComboBoxText">
 *   <items>
67 68 69
 *     <item translatable="yes" id="factory">Factory</item>
 *     <item translatable="yes" id="home">Home</item>
 *     <item translatable="yes" id="subway">Subway</item>
70 71
 *   </items>
 * </object>
72
 * ]|
73 74 75 76
 *
 * # CSS nodes
 *
 * |[<!-- language="plain" -->
77 78 79 80 81
 * combobox
 * ╰── box.linked
 *     ├── entry.combo
 *     ├── button.combo
 *     ╰── window.popup
82 83 84 85
 * ]|
 *
 * GtkComboBoxText has a single CSS node with name combobox. It adds
 * the style class .combo to the main CSS nodes of its entry and button
86
 * children, and the .linked class to the node of its internal box.
87 88
 */

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
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));
108

109 110
static void
gtk_combo_box_text_constructed (GObject *object)
111
{
112
  const gint text_column = 0;
113

114
  G_OBJECT_CLASS (gtk_combo_box_text_parent_class)->constructed (object);
115

116 117 118
  gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (object), text_column);
  gtk_combo_box_set_id_column (GTK_COMBO_BOX (object), 1);

119 120 121 122 123 124 125
  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,
126
                                      "text", text_column,
127 128
                                      NULL);
    }
129 130 131 132 133 134 135
}

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

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

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

  object_class = (GObjectClass *)klass;
147
  object_class->constructed = gtk_combo_box_text_constructed;
148 149
}

150 151 152 153 154 155 156 157 158 159 160 161 162
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;
163
  gchar         *id;
164

165 166
  GString       *string;

167 168 169 170 171 172 173
  gchar         *context;
  guint          translatable : 1;

  guint          is_text : 1;
} ItemParserData;

static void
174 175 176 177 178 179
item_start_element (GMarkupParseContext  *context,
                    const gchar          *element_name,
                    const gchar         **names,
                    const gchar         **values,
                    gpointer              user_data,
                    GError              **error)
180 181 182
{
  ItemParserData *data = (ItemParserData*)user_data;

183
  if (strcmp (element_name, "items") == 0)
184
    {
185 186
      if (!_gtk_builder_check_parent (data->builder, context, "object", error))
        return;
187

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
      if (!g_markup_collect_attributes (element_name, names, values, error,
                                        G_MARKUP_COLLECT_INVALID, NULL, NULL,
                                        G_MARKUP_COLLECT_INVALID))
        _gtk_builder_prefix_error (data->builder, context, error);
    }
  else if (strcmp (element_name, "item") == 0)
    {
      const gchar *id = NULL;
      gboolean translatable = FALSE;
      const gchar *msg_context = NULL;

      if (!_gtk_builder_check_parent (data->builder, context, "items", error))
        return;

      if (!g_markup_collect_attributes (element_name, names, values, error,
                                        G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "id", &id,
                                        G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable", &translatable,
                                        G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments", NULL,
                                        G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context", &msg_context,
                                        G_MARKUP_COLLECT_INVALID))
        {
          _gtk_builder_prefix_error (data->builder, context, error);
          return;
        }

      data->is_text = TRUE;
      data->translatable = translatable;
      data->context = g_strdup (msg_context);
      data->id = g_strdup (id);
    }
  else
    {
      _gtk_builder_error_unhandled_tag (data->builder, context,
                                        "GtkComboBoxText", element_name,
                                        error);
223 224 225 226
    }
}

static void
227 228 229 230 231
item_text (GMarkupParseContext  *context,
           const gchar          *text,
           gsize                 text_len,
           gpointer              user_data,
           GError              **error)
232 233 234
{
  ItemParserData *data = (ItemParserData*)user_data;

235 236
  if (data->is_text)
    g_string_append_len (data->string, text, text_len);
237 238 239
}

static void
240 241 242 243
item_end_element (GMarkupParseContext  *context,
                  const gchar          *element_name,
                  gpointer              user_data,
                  GError              **error)
244 245 246 247
{
  ItemParserData *data = (ItemParserData*)user_data;

  /* Append the translated strings */
248 249 250 251
  if (data->string->len)
    {
      if (data->translatable)
	{
252
	  const gchar *translated;
253 254 255 256

	  translated = _gtk_builder_parser_translate (data->domain,
						      data->context,
						      data->string->str);
257
	  g_string_assign (data->string, translated);
258 259
	}

260
      gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (data->object), data->id, data->string->str);
261
    }
262 263

  data->translatable = FALSE;
264
  g_string_set_size (data->string, 0);
265 266
  g_clear_pointer (&data->context, g_free);
  g_clear_pointer (&data->id, g_free);
267 268 269 270 271 272 273 274 275 276 277
  data->is_text = FALSE;
}

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

static gboolean
278 279 280 281 282 283
gtk_combo_box_text_buildable_custom_tag_start (GtkBuildable  *buildable,
                                               GtkBuilder    *builder,
                                               GObject       *child,
                                               const gchar   *tagname,
                                               GMarkupParser *parser,
                                               gpointer      *parser_data)
284
{
285 286
  if (buildable_parent_iface->custom_tag_start (buildable, builder, child,
						tagname, parser, parser_data))
287 288 289 290
    return TRUE;

  if (strcmp (tagname, "items") == 0)
    {
291 292 293 294 295 296 297
      ItemParserData *data;

      data = g_slice_new0 (ItemParserData);
      data->builder = g_object_ref (builder);
      data->object = g_object_ref (buildable);
      data->domain = gtk_builder_get_translation_domain (builder);
      data->string = g_string_new ("");
298 299

      *parser = item_parser;
300 301
      *parser_data = data;

302 303
      return TRUE;
    }
304

305 306 307 308 309
  return FALSE;
}

static void
gtk_combo_box_text_buildable_custom_finished (GtkBuildable *buildable,
310 311 312 313
                                              GtkBuilder   *builder,
                                              GObject      *child,
                                              const gchar  *tagname,
                                              gpointer      user_data)
314 315 316
{
  ItemParserData *data;

317
  buildable_parent_iface->custom_finished (buildable, builder, child,
318 319 320 321 322 323 324 325
					   tagname, user_data);

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

      g_object_unref (data->object);
      g_object_unref (data->builder);
326
      g_string_free (data->string, TRUE);
327 328 329
      g_slice_free (ItemParserData, data);
    }
}
330

331 332 333 334
/**
 * gtk_combo_box_text_new:
 *
 * Creates a new #GtkComboBoxText, which is a #GtkComboBox just displaying
335
 * strings.
336
 *
337
 * Returns: A new #GtkComboBoxText
338 339 340 341 342 343
 *
 * Since: 2.24
 */
GtkWidget *
gtk_combo_box_text_new (void)
{
344 345
  return g_object_new (GTK_TYPE_COMBO_BOX_TEXT,
                       NULL);
346 347
}

348 349 350 351 352 353
/**
 * 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.
 *
354
 * Returns: a new #GtkComboBoxText
355 356 357 358 359 360 361 362 363 364 365
 *
 * 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);
}

366 367 368 369 370
/**
 * gtk_combo_box_text_append_text:
 * @combo_box: A #GtkComboBoxText
 * @text: A string
 *
371
 * Appends @text to the list of strings stored in @combo_box.
372
 *
373 374 375
 * This is the same as calling gtk_combo_box_text_insert_text() with a
 * position of -1.
 *
376 377 378 379 380 381
 * Since: 2.24
 */
void
gtk_combo_box_text_append_text (GtkComboBoxText *combo_box,
                                const gchar     *text)
{
382
  gtk_combo_box_text_insert (combo_box, -1, NULL, text);
383
}
384

385 386 387 388 389
/**
 * gtk_combo_box_text_prepend_text:
 * @combo_box: A #GtkComboBox
 * @text: A string
 *
390
 * Prepends @text to the list of strings stored in @combo_box.
391
 *
392 393 394
 * This is the same as calling gtk_combo_box_text_insert_text() with a
 * position of 0.
 *
395 396 397 398 399 400
 * Since: 2.24
 */
void
gtk_combo_box_text_prepend_text (GtkComboBoxText *combo_box,
                                 const gchar     *text)
{
401
  gtk_combo_box_text_insert (combo_box, 0, NULL, text);
402 403 404 405 406 407 408 409
}

/**
 * gtk_combo_box_text_insert_text:
 * @combo_box: A #GtkComboBoxText
 * @position: An index to insert @text
 * @text: A string
 *
410
 * Inserts @text at @position in the list of strings stored in @combo_box.
411
 *
412 413 414 415 416
 * If @position is negative then @text is appended.
 *
 * This is the same as calling gtk_combo_box_text_insert() with a %NULL
 * ID string.
 *
417 418 419 420 421 422
 * Since: 2.24
 */
void
gtk_combo_box_text_insert_text (GtkComboBoxText *combo_box,
                                gint             position,
                                const gchar     *text)
423 424 425 426 427 428 429
{
  gtk_combo_box_text_insert (combo_box, position, NULL, text);
}

/**
 * gtk_combo_box_text_append:
 * @combo_box: A #GtkComboBoxText
430
 * @id: (allow-none): a string ID for this value, or %NULL
431 432
 * @text: A string
 *
433 434
 * 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.
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
 *
 * 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
452 453
 * @id: (allow-none): a string ID for this value, or %NULL
 * @text: a string
454
 *
455 456
 * 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.
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
 *
 * 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
476
 * @id: (allow-none): a string ID for this value, or %NULL
477 478 479 480
 * @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
481
 * #GtkComboBox:id-column.
482 483 484 485 486 487 488 489 490 491
 *
 * 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)
492 493 494
{
  GtkListStore *store;
  GtkTreeIter iter;
495
  gint text_column;
496 497 498 499 500 501 502
  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));
503

504
  text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
505 506 507 508 509 510

  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;

511
  column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), text_column);
512 513
  g_return_if_fail (column_type == G_TYPE_STRING);

514 515 516 517 518
  if (position < 0)
    gtk_list_store_append (store, &iter);
  else
    gtk_list_store_insert (store, &iter, position);

519
  gtk_list_store_set (store, &iter, text_column, text, -1);
520 521 522 523 524 525

  if (id != NULL)
    {
      gint id_column;

      id_column = gtk_combo_box_get_id_column (GTK_COMBO_BOX (combo_box));
526
      g_return_if_fail (id_column >= 0);
527 528 529 530 531
      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);
    }
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
}

/**
 * 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);
}

562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
/**
 * 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);
}

581 582 583 584
/**
 * gtk_combo_box_text_get_active_text:
 * @combo_box: A #GtkComboBoxText
 *
585 586 587 588
 * 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).
589
 *
590 591
 * Returns: (transfer full): a newly allocated string containing the
 *     currently active text. Must be freed with g_free().
592 593 594 595 596 597 598 599 600 601 602
 *
 * 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);

603 604 605 606 607 608 609 610
 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))
611 612
    {
      GtkTreeModel *model;
613
      gint text_column;
614 615 616 617
      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);
618
      text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
619
      g_return_val_if_fail (text_column >= 0, NULL);
620
      column_type = gtk_tree_model_get_column_type (model, text_column);
621
      g_return_val_if_fail (column_type == G_TYPE_STRING, NULL);
622
      gtk_tree_model_get (model, &iter, text_column, &text, -1);
623 624 625 626
    }

  return text;
}