1/* gtkcombobox.c
2 * Copyright (C) 2002, 2003 Kristian Rietveld <kris@gtk.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gtkcomboboxprivate.h"
21
22#include "gtkbox.h"
23#include "gtkcellareabox.h"
24#include "gtkcelllayout.h"
25#include "gtkcellrenderertext.h"
26#include "gtkcellview.h"
27#include "gtkeventcontrollerkey.h"
28#include "gtkeventcontrollerscroll.h"
29#include "gtkframe.h"
30#include "gtkbuiltiniconprivate.h"
31#include "gtkintl.h"
32#include "gtkliststore.h"
33#include "gtkmain.h"
34#include "gtkmarshalers.h"
35#include "gtkprivate.h"
36#include "gtkshortcutcontroller.h"
37#include "gtktogglebutton.h"
38#include "gtktreepopoverprivate.h"
39#include "gtktypebuiltins.h"
40#include "gtkwidgetprivate.h"
41
42#include <gobject/gvaluecollector.h>
43#include <string.h>
44#include <stdarg.h>
45
46/**
47 * GtkComboBox:
48 *
49 * A `GtkComboBox` is a widget that allows the user to choose from a list of
50 * valid choices.
51 *
52 * ![An example GtkComboBox](combo-box.png)
53 *
54 * The `GtkComboBox` displays the selected choice; when activated, the
55 * `GtkComboBox` displays a popup which allows the user to make a new choice.
56 *
57 * The `GtkComboBox` uses the model-view pattern; the list of valid choices
58 * is specified in the form of a tree model, and the display of the choices
59 * can be adapted to the data in the model by using cell renderers, as you
60 * would in a tree view. This is possible since `GtkComboBox` implements the
61 * [iface@Gtk.CellLayout] interface. The tree model holding the valid
62 * choices is not restricted to a flat list, it can be a real tree, and the
63 * popup will reflect the tree structure.
64 *
65 * To allow the user to enter values not in the model, the
66 * [property@Gtk.ComboBox:has-entry] property allows the `GtkComboBox` to
67 * contain a [class@Gtk.Entry]. This entry can be accessed by calling
68 * [method@Gtk.ComboBox.get_child] on the combo box.
69 *
70 * For a simple list of textual choices, the model-view API of `GtkComboBox`
71 * can be a bit overwhelming. In this case, [class@Gtk.ComboBoxText] offers
72 * a simple alternative. Both `GtkComboBox` and `GtkComboBoxText` can contain
73 * an entry.
74 *
75 * ## CSS nodes
76 *
77 * ```
78 * combobox
79 * ├── box.linked
80 * │ ╰── button.combo
81 * │ ╰── box
82 * │ ├── cellview
83 * │ ╰── arrow
84 * ╰── window.popup
85 * ```
86 *
87 * A normal combobox contains a box with the .linked class, a button
88 * with the .combo class and inside those buttons, there are a cellview and
89 * an arrow.
90 *
91 * ```
92 * combobox
93 * ├── box.linked
94 * │ ├── entry.combo
95 * │ ╰── button.combo
96 * │ ╰── box
97 * │ ╰── arrow
98 * ╰── window.popup
99 * ```
100 *
101 * A `GtkComboBox` with an entry has a single CSS node with name combobox.
102 * It contains a box with the .linked class. That box contains an entry and
103 * a button, both with the .combo class added. The button also contains another
104 * node with name arrow.
105 *
106 * # Accessibility
107 *
108 * `GtkComboBox` uses the %GTK_ACCESSIBLE_ROLE_COMBO_BOX role.
109 */
110
111typedef struct
112{
113 GtkWidget *child;
114
115 GtkTreeModel *model;
116
117 GtkCellArea *area;
118
119 int active; /* Only temporary */
120 GtkTreeRowReference *active_row;
121
122 GtkWidget *cell_view;
123
124 GtkWidget *box;
125 GtkWidget *button;
126 GtkWidget *arrow;
127
128 GtkWidget *popup_widget;
129
130 guint popup_idle_id;
131 guint scroll_timer;
132 guint resize_idle_id;
133
134 /* For "has-entry" specific behavior we track
135 * an automated cell renderer and text column
136 */
137 int text_column;
138 GtkCellRenderer *text_renderer;
139
140 int id_column;
141
142 guint popup_in_progress : 1;
143 guint popup_shown : 1;
144 guint has_frame : 1;
145 guint is_cell_renderer : 1;
146 guint editing_canceled : 1;
147 guint auto_scroll : 1;
148 guint button_sensitivity : 2;
149 guint has_entry : 1;
150 guint popup_fixed_width : 1;
151
152 GtkTreeViewRowSeparatorFunc row_separator_func;
153 gpointer row_separator_data;
154 GDestroyNotify row_separator_destroy;
155} GtkComboBoxPrivate;
156
157/* There are 2 modes to this widget, which can be characterized as follows:
158 *
159 * 1) no child added:
160 *
161 * cell_view -> GtkCellView, regular child
162 * button -> GtkToggleButton set_parent to combo
163 * arrow -> GtkArrow set_parent to button
164 * popup_widget -> GtkMenu
165 *
166 * 2) child added:
167 *
168 * cell_view -> NULL
169 * button -> GtkToggleButton set_parent to combo
170 * arrow -> GtkArrow, child of button
171 * popup_widget -> GtkMenu
172 */
173
174enum {
175 ACTIVATE,
176 CHANGED,
177 MOVE_ACTIVE,
178 POPUP,
179 POPDOWN,
180 FORMAT_ENTRY_TEXT,
181 LAST_SIGNAL
182};
183
184enum {
185 PROP_0,
186 PROP_MODEL,
187 PROP_ACTIVE,
188 PROP_HAS_FRAME,
189 PROP_POPUP_SHOWN,
190 PROP_BUTTON_SENSITIVITY,
191 PROP_EDITING_CANCELED,
192 PROP_HAS_ENTRY,
193 PROP_ENTRY_TEXT_COLUMN,
194 PROP_POPUP_FIXED_WIDTH,
195 PROP_ID_COLUMN,
196 PROP_ACTIVE_ID,
197 PROP_CHILD
198};
199
200static guint combo_box_signals[LAST_SIGNAL] = {0,};
201
202/* common */
203
204static void gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface);
205static void gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface);
206static void gtk_combo_box_constructed (GObject *object);
207static void gtk_combo_box_dispose (GObject *object);
208static void gtk_combo_box_unmap (GtkWidget *widget);
209
210static void gtk_combo_box_set_property (GObject *object,
211 guint prop_id,
212 const GValue *value,
213 GParamSpec *spec);
214static void gtk_combo_box_get_property (GObject *object,
215 guint prop_id,
216 GValue *value,
217 GParamSpec *spec);
218
219static gboolean gtk_combo_box_grab_focus (GtkWidget *widget);
220static void gtk_combo_box_button_toggled (GtkWidget *widget,
221 gpointer data);
222
223static void gtk_combo_box_menu_show (GtkWidget *menu,
224 gpointer user_data);
225static void gtk_combo_box_menu_hide (GtkWidget *menu,
226 gpointer user_data);
227
228static void gtk_combo_box_unset_model (GtkComboBox *combo_box);
229
230static void gtk_combo_box_set_active_internal (GtkComboBox *combo_box,
231 GtkTreePath *path);
232
233static void gtk_combo_box_real_move_active (GtkComboBox *combo_box,
234 GtkScrollType scroll);
235static void gtk_combo_box_real_popup (GtkComboBox *combo_box);
236static gboolean gtk_combo_box_real_popdown (GtkComboBox *combo_box);
237
238static gboolean gtk_combo_box_scroll_controller_scroll (GtkEventControllerScroll *scroll,
239 double dx,
240 double dy,
241 GtkComboBox *combo_box);
242
243/* listening to the model */
244static void gtk_combo_box_model_row_inserted (GtkTreeModel *model,
245 GtkTreePath *path,
246 GtkTreeIter *iter,
247 gpointer user_data);
248static void gtk_combo_box_model_row_deleted (GtkTreeModel *model,
249 GtkTreePath *path,
250 gpointer user_data);
251static void gtk_combo_box_model_rows_reordered (GtkTreeModel *model,
252 GtkTreePath *path,
253 GtkTreeIter *iter,
254 int *new_order,
255 gpointer user_data);
256static void gtk_combo_box_model_row_changed (GtkTreeModel *model,
257 GtkTreePath *path,
258 GtkTreeIter *iter,
259 gpointer data);
260
261static void gtk_combo_box_menu_activate (GtkWidget *menu,
262 const char *path,
263 GtkComboBox *combo_box);
264static void gtk_combo_box_update_sensitivity (GtkComboBox *combo_box);
265static gboolean gtk_combo_box_menu_key (GtkEventControllerKey *key,
266 guint keyval,
267 guint keycode,
268 GdkModifierType modifiers,
269 GtkComboBox *combo_box);
270static void gtk_combo_box_menu_popup (GtkComboBox *combo_box);
271
272/* cell layout */
273static GtkCellArea *gtk_combo_box_cell_layout_get_area (GtkCellLayout *cell_layout);
274
275static gboolean gtk_combo_box_mnemonic_activate (GtkWidget *widget,
276 gboolean group_cycling);
277
278static void gtk_combo_box_child_show (GtkWidget *widget,
279 GtkComboBox *combo_box);
280static void gtk_combo_box_child_hide (GtkWidget *widget,
281 GtkComboBox *combo_box);
282
283/* GtkComboBox:has-entry callbacks */
284static void gtk_combo_box_entry_contents_changed (GtkEntry *entry,
285 gpointer user_data);
286static void gtk_combo_box_entry_active_changed (GtkComboBox *combo_box,
287 gpointer user_data);
288static char *gtk_combo_box_format_entry_text (GtkComboBox *combo_box,
289 const char *path);
290
291/* GtkBuildable method implementation */
292static GtkBuildableIface *parent_buildable_iface;
293
294static void gtk_combo_box_buildable_init (GtkBuildableIface *iface);
295static void gtk_combo_box_buildable_add_child (GtkBuildable *buildable,
296 GtkBuilder *builder,
297 GObject *child,
298 const char *type);
299static gboolean gtk_combo_box_buildable_custom_tag_start (GtkBuildable *buildable,
300 GtkBuilder *builder,
301 GObject *child,
302 const char *tagname,
303 GtkBuildableParser *parser,
304 gpointer *data);
305static void gtk_combo_box_buildable_custom_tag_end (GtkBuildable *buildable,
306 GtkBuilder *builder,
307 GObject *child,
308 const char *tagname,
309 gpointer data);
310static GObject *gtk_combo_box_buildable_get_internal_child (GtkBuildable *buildable,
311 GtkBuilder *builder,
312 const char *childname);
313
314
315
316/* GtkCellEditable method implementations */
317static void gtk_combo_box_start_editing (GtkCellEditable *cell_editable,
318 GdkEvent *event);
319
320G_DEFINE_TYPE_WITH_CODE (GtkComboBox, gtk_combo_box, GTK_TYPE_WIDGET,
321 G_ADD_PRIVATE (GtkComboBox)
322 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
323 gtk_combo_box_cell_layout_init)
324 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE,
325 gtk_combo_box_cell_editable_init)
326 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
327 gtk_combo_box_buildable_init))
328
329
330/* common */
331static void
332gtk_combo_box_measure (GtkWidget *widget,
333 GtkOrientation orientation,
334 int size,
335 int *minimum,
336 int *natural,
337 int *minimum_baseline,
338 int *natural_baseline)
339{
340 GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
341 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
342
343 gtk_widget_measure (widget: priv->box,
344 orientation,
345 for_size: size,
346 minimum, natural,
347 minimum_baseline, natural_baseline);
348}
349
350static void
351gtk_combo_box_activate (GtkComboBox *combo_box)
352{
353 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
354
355 gtk_widget_activate (widget: priv->button);
356}
357
358static void
359gtk_combo_box_size_allocate (GtkWidget *widget,
360 int width,
361 int height,
362 int baseline)
363{
364 GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
365 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
366 int menu_width;
367
368 gtk_widget_size_allocate (widget: priv->box,
369 allocation: &(GtkAllocation) {
370 0, 0,
371 width, height
372 }, baseline);
373
374 gtk_widget_set_size_request (widget: priv->popup_widget, width: -1, height: -1);
375
376 if (priv->popup_fixed_width)
377 gtk_widget_measure (widget: priv->popup_widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
378 minimum: &menu_width, NULL, NULL, NULL);
379 else
380 gtk_widget_measure (widget: priv->popup_widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
381 NULL, natural: &menu_width, NULL, NULL);
382
383 gtk_widget_set_size_request (widget: priv->popup_widget,
384 MAX (width, menu_width), height: -1);
385
386 gtk_popover_present (GTK_POPOVER (priv->popup_widget));
387}
388
389static void
390gtk_combo_box_compute_expand (GtkWidget *widget,
391 gboolean *hexpand,
392 gboolean *vexpand)
393{
394 GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
395 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
396 GtkWidget *child = priv->child;
397
398 if (child && child != priv->cell_view)
399 {
400 *hexpand = gtk_widget_compute_expand (widget: child, orientation: GTK_ORIENTATION_HORIZONTAL);
401 *vexpand = gtk_widget_compute_expand (widget: child, orientation: GTK_ORIENTATION_VERTICAL);
402 }
403 else
404 {
405 *hexpand = FALSE;
406 *vexpand = FALSE;
407 }
408}
409
410static void
411gtk_combo_box_class_init (GtkComboBoxClass *klass)
412{
413 GObjectClass *object_class;
414 GtkWidgetClass *widget_class;
415
416 widget_class = (GtkWidgetClass *)klass;
417 widget_class->mnemonic_activate = gtk_combo_box_mnemonic_activate;
418 widget_class->grab_focus = gtk_combo_box_grab_focus;
419 widget_class->focus = gtk_widget_focus_child;
420 widget_class->measure = gtk_combo_box_measure;
421 widget_class->size_allocate = gtk_combo_box_size_allocate;
422 widget_class->unmap = gtk_combo_box_unmap;
423 widget_class->compute_expand = gtk_combo_box_compute_expand;
424
425 object_class = (GObjectClass *)klass;
426 object_class->constructed = gtk_combo_box_constructed;
427 object_class->dispose = gtk_combo_box_dispose;
428 object_class->set_property = gtk_combo_box_set_property;
429 object_class->get_property = gtk_combo_box_get_property;
430
431 klass->activate = gtk_combo_box_activate;
432 klass->format_entry_text = gtk_combo_box_format_entry_text;
433
434 /* signals */
435 /**
436 * GtkComboBox::activate:
437 * @widget: the object which received the signal.
438 *
439 * Emitted to when the combo box is activated.
440 *
441 * The `::activate` signal on `GtkComboBox` is an action signal and
442 * emitting it causes the combo box to pop up its dropdown.
443 *
444 * Since: 4.6
445 */
446 combo_box_signals[ACTIVATE] =
447 g_signal_new (I_ ("activate"),
448 G_OBJECT_CLASS_TYPE (object_class),
449 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
450 G_STRUCT_OFFSET (GtkComboBoxClass, activate),
451 NULL, NULL,
452 NULL,
453 G_TYPE_NONE, n_params: 0);
454
455 gtk_widget_class_set_activate_signal (widget_class, signal_id: combo_box_signals[ACTIVATE]);
456
457 /**
458 * GtkComboBox::changed:
459 * @widget: the object which received the signal
460 *
461 * Emitted when the active item is changed.
462 *
463 * The can be due to the user selecting a different item from the list,
464 * or due to a call to [method@Gtk.ComboBox.set_active_iter]. It will
465 * also be emitted while typing into the entry of a combo box with an entry.
466 */
467 combo_box_signals[CHANGED] =
468 g_signal_new (I_("changed"),
469 G_OBJECT_CLASS_TYPE (klass),
470 signal_flags: G_SIGNAL_RUN_LAST,
471 G_STRUCT_OFFSET (GtkComboBoxClass, changed),
472 NULL, NULL,
473 NULL,
474 G_TYPE_NONE, n_params: 0);
475
476 /**
477 * GtkComboBox::move-active:
478 * @widget: the object that received the signal
479 * @scroll_type: a `GtkScrollType`
480 *
481 * Emitted to move the active selection.
482 *
483 * This is an [keybinding signal](class.SignalAction.html).
484 */
485 combo_box_signals[MOVE_ACTIVE] =
486 g_signal_new_class_handler (I_("move-active"),
487 G_OBJECT_CLASS_TYPE (klass),
488 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
489 G_CALLBACK (gtk_combo_box_real_move_active),
490 NULL, NULL,
491 NULL,
492 G_TYPE_NONE, n_params: 1,
493 GTK_TYPE_SCROLL_TYPE);
494
495 /**
496 * GtkComboBox::popup:
497 * @widget: the object that received the signal
498 *
499 * Emitted to popup the combo box list.
500 *
501 * This is an [keybinding signal](class.SignalAction.html).
502 *
503 * The default binding for this signal is Alt+Down.
504 */
505 combo_box_signals[POPUP] =
506 g_signal_new_class_handler (I_("popup"),
507 G_OBJECT_CLASS_TYPE (klass),
508 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
509 G_CALLBACK (gtk_combo_box_real_popup),
510 NULL, NULL,
511 NULL,
512 G_TYPE_NONE, n_params: 0);
513 /**
514 * GtkComboBox::popdown:
515 * @button: the object which received the signal
516 *
517 * Emitted to popdown the combo box list.
518 *
519 * This is an [keybinding signal](class.SignalAction.html).
520 *
521 * The default bindings for this signal are Alt+Up and Escape.
522 */
523 combo_box_signals[POPDOWN] =
524 g_signal_new_class_handler (I_("popdown"),
525 G_OBJECT_CLASS_TYPE (klass),
526 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
527 G_CALLBACK (gtk_combo_box_real_popdown),
528 NULL, NULL,
529 c_marshaller: _gtk_marshal_BOOLEAN__VOID,
530 G_TYPE_BOOLEAN, n_params: 0);
531
532 /**
533 * GtkComboBox::format-entry-text:
534 * @combo: the object which received the signal
535 * @path: the [struct@Gtk.TreePath] string from the combo box's current model
536 * to format text for
537 *
538 * Emitted to allow changing how the text in a combo box's entry is displayed.
539 *
540 * See [property@Gtk.ComboBox:has-entry].
541 *
542 * Connect a signal handler which returns an allocated string representing
543 * @path. That string will then be used to set the text in the combo box's
544 * entry. The default signal handler uses the text from the
545 * [property@Gtk.ComboBox:entry-text-column] model column.
546 *
547 * Here's an example signal handler which fetches data from the model and
548 * displays it in the entry.
549 * ```c
550 * static char *
551 * format_entry_text_callback (GtkComboBox *combo,
552 * const char *path,
553 * gpointer user_data)
554 * {
555 * GtkTreeIter iter;
556 * GtkTreeModel model;
557 * double value;
558 *
559 * model = gtk_combo_box_get_model (combo);
560 *
561 * gtk_tree_model_get_iter_from_string (model, &iter, path);
562 * gtk_tree_model_get (model, &iter,
563 * THE_DOUBLE_VALUE_COLUMN, &value,
564 * -1);
565 *
566 * return g_strdup_printf ("%g", value);
567 * }
568 * ```
569 *
570 * Returns: (transfer full): a newly allocated string representing @path
571 * for the current `GtkComboBox` model.
572 */
573 combo_box_signals[FORMAT_ENTRY_TEXT] =
574 g_signal_new (I_("format-entry-text"),
575 G_TYPE_FROM_CLASS (klass),
576 signal_flags: G_SIGNAL_RUN_LAST,
577 G_STRUCT_OFFSET (GtkComboBoxClass, format_entry_text),
578 accumulator: _gtk_single_string_accumulator, NULL,
579 c_marshaller: _gtk_marshal_STRING__STRING,
580 G_TYPE_STRING, n_params: 1, G_TYPE_STRING);
581
582 /* key bindings */
583 gtk_widget_class_add_binding_signal (widget_class,
584 GDK_KEY_Down, mods: GDK_ALT_MASK,
585 signal: "popup",
586 NULL);
587 gtk_widget_class_add_binding_signal (widget_class,
588 GDK_KEY_KP_Down, mods: GDK_ALT_MASK,
589 signal: "popup",
590 NULL);
591
592 gtk_widget_class_add_binding_signal (widget_class,
593 GDK_KEY_Up, mods: GDK_ALT_MASK,
594 signal: "popdown",
595 NULL);
596 gtk_widget_class_add_binding_signal (widget_class,
597 GDK_KEY_KP_Up, mods: GDK_ALT_MASK,
598 signal: "popdown",
599 NULL);
600 gtk_widget_class_add_binding_signal (widget_class,
601 GDK_KEY_Escape, mods: 0,
602 signal: "popdown",
603 NULL);
604
605 gtk_widget_class_add_binding_signal (widget_class,
606 GDK_KEY_Up, mods: 0,
607 signal: "move-active",
608 format_string: "(i)", GTK_SCROLL_STEP_UP);
609 gtk_widget_class_add_binding_signal (widget_class,
610 GDK_KEY_KP_Up, mods: 0,
611 signal: "move-active",
612 format_string: "(i)", GTK_SCROLL_STEP_UP);
613 gtk_widget_class_add_binding_signal (widget_class,
614 GDK_KEY_Page_Up, mods: 0,
615 signal: "move-active",
616 format_string: "(i)", GTK_SCROLL_PAGE_UP);
617 gtk_widget_class_add_binding_signal (widget_class,
618 GDK_KEY_KP_Page_Up, mods: 0,
619 signal: "move-active",
620 format_string: "(i)", GTK_SCROLL_PAGE_UP);
621 gtk_widget_class_add_binding_signal (widget_class,
622 GDK_KEY_Home, mods: 0,
623 signal: "move-active",
624 format_string: "(i)", GTK_SCROLL_START);
625 gtk_widget_class_add_binding_signal (widget_class,
626 GDK_KEY_KP_Home, mods: 0,
627 signal: "move-active",
628 format_string: "(i)", GTK_SCROLL_START);
629
630 gtk_widget_class_add_binding_signal (widget_class,
631 GDK_KEY_Down, mods: 0,
632 signal: "move-active",
633 format_string: "(i)", GTK_SCROLL_STEP_DOWN);
634 gtk_widget_class_add_binding_signal (widget_class,
635 GDK_KEY_KP_Down, mods: 0,
636 signal: "move-active",
637 format_string: "(i)", GTK_SCROLL_STEP_DOWN);
638 gtk_widget_class_add_binding_signal (widget_class,
639 GDK_KEY_Page_Down, mods: 0,
640 signal: "move-active",
641 format_string: "(i)", GTK_SCROLL_PAGE_DOWN);
642 gtk_widget_class_add_binding_signal (widget_class,
643 GDK_KEY_KP_Page_Down, mods: 0,
644 signal: "move-active",
645 format_string: "(i)", GTK_SCROLL_PAGE_DOWN);
646 gtk_widget_class_add_binding_signal (widget_class,
647 GDK_KEY_End, mods: 0,
648 signal: "move-active",
649 format_string: "(i)", GTK_SCROLL_END);
650 gtk_widget_class_add_binding_signal (widget_class,
651 GDK_KEY_KP_End, mods: 0,
652 signal: "move-active",
653 format_string: "(i)", GTK_SCROLL_END);
654
655 /* properties */
656 g_object_class_override_property (oclass: object_class,
657 property_id: PROP_EDITING_CANCELED,
658 name: "editing-canceled");
659
660 /**
661 * GtkComboBox:model: (attributes org.gtk.Property.get=gtk_combo_box_get_model org.gtk.Property.set=gtk_combo_box_set_model)
662 *
663 * The model from which the combo box takes its values.
664 */
665 g_object_class_install_property (oclass: object_class,
666 property_id: PROP_MODEL,
667 pspec: g_param_spec_object (name: "model",
668 P_("ComboBox model"),
669 P_("The model for the combo box"),
670 GTK_TYPE_TREE_MODEL,
671 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
672
673
674 /**
675 * GtkComboBox:active: (attributes org.gtk.Property.get=gtk_combo_box_get_active org.gtk.Property.set=gtk_combo_box_set_active)
676 *
677 * The item which is currently active.
678 *
679 * If the model is a non-flat treemodel, and the active item is not an
680 * immediate child of the root of the tree, this property has the value
681 * `gtk_tree_path_get_indices (path)[0]`, where `path` is the
682 * [struct@Gtk.TreePath] of the active item.
683 */
684 g_object_class_install_property (oclass: object_class,
685 property_id: PROP_ACTIVE,
686 pspec: g_param_spec_int (name: "active",
687 P_("Active item"),
688 P_("The item which is currently active"),
689 minimum: -1,
690 G_MAXINT,
691 default_value: -1,
692 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
693
694 /**
695 * GtkComboBox:has-frame:
696 *
697 * The `has-frame` property controls whether a frame is drawn around the entry.
698 */
699 g_object_class_install_property (oclass: object_class,
700 property_id: PROP_HAS_FRAME,
701 pspec: g_param_spec_boolean (name: "has-frame",
702 P_("Has Frame"),
703 P_("Whether the combo box draws a frame around the child"),
704 TRUE,
705 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
706
707 /**
708 * GtkComboBox:popup-shown:
709 *
710 * Whether the combo boxes dropdown is popped up.
711 *
712 * Note that this property is mainly useful, because
713 * it allows you to connect to notify::popup-shown.
714 */
715 g_object_class_install_property (oclass: object_class,
716 property_id: PROP_POPUP_SHOWN,
717 pspec: g_param_spec_boolean (name: "popup-shown",
718 P_("Popup shown"),
719 P_("Whether the combo’s dropdown is shown"),
720 FALSE,
721 GTK_PARAM_READABLE));
722
723
724 /**
725 * GtkComboBox:button-sensitivity: (attributes org.gtk.Property.get=gtk_combo_box_get_button_sensitivity org.gtk.Property.set=gtk_combo_box_set_button_sensitivity)
726 *
727 * Whether the dropdown button is sensitive when
728 * the model is empty.
729 */
730 g_object_class_install_property (oclass: object_class,
731 property_id: PROP_BUTTON_SENSITIVITY,
732 pspec: g_param_spec_enum (name: "button-sensitivity",
733 P_("Button Sensitivity"),
734 P_("Whether the dropdown button is sensitive when the model is empty"),
735 enum_type: GTK_TYPE_SENSITIVITY_TYPE,
736 default_value: GTK_SENSITIVITY_AUTO,
737 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
738
739 /**
740 * GtkComboBox:has-entry: (attributes org.gtk.Property.get=gtk_combo_box_get_has_entry)
741 *
742 * Whether the combo box has an entry.
743 */
744 g_object_class_install_property (oclass: object_class,
745 property_id: PROP_HAS_ENTRY,
746 pspec: g_param_spec_boolean (name: "has-entry",
747 P_("Has Entry"),
748 P_("Whether combo box has an entry"),
749 FALSE,
750 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
751
752 /**
753 * GtkComboBox:entry-text-column: (attributes org.gtk.Property.get=gtk_combo_box_get_entry_text_column org.gtk.Property.set=gtk_combo_box_set_entry_text_column)
754 *
755 * The model column to associate with strings from the entry.
756 *
757 * This is property only relevant if the combo was created with
758 * [property@Gtk.ComboBox:has-entry] is %TRUE.
759 */
760 g_object_class_install_property (oclass: object_class,
761 property_id: PROP_ENTRY_TEXT_COLUMN,
762 pspec: g_param_spec_int (name: "entry-text-column",
763 P_("Entry Text Column"),
764 P_("The column in the combo box’s model to associate "
765 "with strings from the entry if the combo was "
766 "created with GtkComboBox:has-entry = %TRUE"),
767 minimum: -1, G_MAXINT, default_value: -1,
768 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
769
770 /**
771 * GtkComboBox:id-column: (attributes org.gtk.Property.get=gtk_combo_box_get_id_column org.gtk.Property.set=gtk_combo_box_set_id_column)
772 *
773 * The model column that provides string IDs for the values
774 * in the model, if != -1.
775 */
776 g_object_class_install_property (oclass: object_class,
777 property_id: PROP_ID_COLUMN,
778 pspec: g_param_spec_int (name: "id-column",
779 P_("ID Column"),
780 P_("The column in the combo box’s model that provides "
781 "string IDs for the values in the model"),
782 minimum: -1, G_MAXINT, default_value: -1,
783 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
784
785 /**
786 * GtkComboBox:active-id: (attributes org.gtk.Property.get=gtk_combo_box_get_active_id org.gtk.Property.set=gtk_combo_box_set_active_id)
787 *
788 * The value of the ID column of the active row.
789 */
790 g_object_class_install_property (oclass: object_class,
791 property_id: PROP_ACTIVE_ID,
792 pspec: g_param_spec_string (name: "active-id",
793 P_("Active id"),
794 P_("The value of the id column "
795 "for the active row"),
796 NULL,
797 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
798
799 /**
800 * GtkComboBox:popup-fixed-width: (attributes org.gtk.Property.get=gtk_combo_box_get_popup_fixed_width org.gtk.Property.set=gtk_combo_box_set_popup_fixed_width)
801 *
802 * Whether the popup's width should be a fixed width matching the
803 * allocated width of the combo box.
804 */
805 g_object_class_install_property (oclass: object_class,
806 property_id: PROP_POPUP_FIXED_WIDTH,
807 pspec: g_param_spec_boolean (name: "popup-fixed-width",
808 P_("Popup Fixed Width"),
809 P_("Whether the popup’s width should be a "
810 "fixed width matching the allocated width "
811 "of the combo box"),
812 TRUE,
813 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
814
815 /**
816 * GtkComboBox:child: (attributes org.gtk.Property.get=gtk_combo_box_get_child org.gtk.Property.set=gtk_combo_box_set_child)
817 *
818 * The child widget.
819 */
820 g_object_class_install_property (oclass: object_class,
821 property_id: PROP_CHILD,
822 pspec: g_param_spec_object (name: "child",
823 P_("Child"),
824 P_("The child_widget"),
825 GTK_TYPE_WIDGET,
826 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
827
828 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/ui/gtkcombobox.ui");
829 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkComboBox, box);
830 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkComboBox, button);
831 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkComboBox, arrow);
832 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkComboBox, area);
833 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkComboBox, popup_widget);
834 gtk_widget_class_bind_template_callback (widget_class, gtk_combo_box_button_toggled);
835 gtk_widget_class_bind_template_callback (widget_class, gtk_combo_box_menu_activate);
836 gtk_widget_class_bind_template_callback (widget_class, gtk_combo_box_menu_key);
837 gtk_widget_class_bind_template_callback (widget_class, gtk_combo_box_menu_show);
838 gtk_widget_class_bind_template_callback (widget_class, gtk_combo_box_menu_hide);
839
840 gtk_widget_class_set_css_name (widget_class, I_("combobox"));
841 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_COMBO_BOX);
842}
843
844static void
845gtk_combo_box_buildable_init (GtkBuildableIface *iface)
846{
847 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
848 iface->add_child = gtk_combo_box_buildable_add_child;
849 iface->custom_tag_start = gtk_combo_box_buildable_custom_tag_start;
850 iface->custom_tag_end = gtk_combo_box_buildable_custom_tag_end;
851 iface->get_internal_child = gtk_combo_box_buildable_get_internal_child;
852}
853
854static void
855gtk_combo_box_cell_layout_init (GtkCellLayoutIface *iface)
856{
857 iface->get_area = gtk_combo_box_cell_layout_get_area;
858}
859
860static void
861gtk_combo_box_cell_editable_init (GtkCellEditableIface *iface)
862{
863 iface->start_editing = gtk_combo_box_start_editing;
864}
865
866static gboolean
867gtk_combo_box_row_separator_func (GtkTreeModel *model,
868 GtkTreeIter *iter,
869 GtkComboBox *combo_box)
870{
871 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
872
873 if (priv->row_separator_func)
874 return priv->row_separator_func (model, iter, priv->row_separator_data);
875
876 return FALSE;
877}
878
879static void
880gtk_combo_box_init (GtkComboBox *combo_box)
881{
882 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
883 GtkEventController *controller;
884 GtkEventController **controllers;
885 guint n_controllers, i;
886
887 priv->active = -1;
888 priv->active_row = NULL;
889
890 priv->popup_shown = FALSE;
891 priv->has_frame = TRUE;
892 priv->is_cell_renderer = FALSE;
893 priv->editing_canceled = FALSE;
894 priv->auto_scroll = FALSE;
895 priv->button_sensitivity = GTK_SENSITIVITY_AUTO;
896 priv->has_entry = FALSE;
897 priv->popup_fixed_width = TRUE;
898
899 priv->text_column = -1;
900 priv->text_renderer = NULL;
901 priv->id_column = -1;
902
903 g_type_ensure (GTK_TYPE_BUILTIN_ICON);
904 g_type_ensure (GTK_TYPE_TREE_POPOVER);
905 gtk_widget_init_template (GTK_WIDGET (combo_box));
906
907 gtk_widget_remove_css_class (widget: priv->button, css_class: "toggle");
908 gtk_widget_add_css_class (widget: priv->button, css_class: "combo");
909
910 gtk_tree_popover_set_row_separator_func (popover: GTK_TREE_POPOVER (ptr: priv->popup_widget),
911 func: (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func,
912 data: combo_box, NULL);
913
914 controller = gtk_event_controller_scroll_new (flags: GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
915 GTK_EVENT_CONTROLLER_SCROLL_DISCRETE);
916 g_signal_connect (controller, "scroll",
917 G_CALLBACK (gtk_combo_box_scroll_controller_scroll),
918 combo_box);
919 gtk_widget_add_controller (GTK_WIDGET (combo_box), controller);
920
921 controllers = gtk_widget_list_controllers (widget: priv->popup_widget, phase: GTK_PHASE_BUBBLE, out_n_controllers: &n_controllers);
922 for (i = 0; i < n_controllers; i ++)
923 {
924 controller = controllers[i];
925
926 if (GTK_IS_SHORTCUT_CONTROLLER (controller))
927 {
928 g_object_ref (controller);
929 gtk_widget_remove_controller (widget: priv->popup_widget, controller);
930 gtk_widget_add_controller (widget: priv->popup_widget, controller);
931 break;
932 }
933 }
934 g_free (mem: controllers);
935}
936
937static void
938gtk_combo_box_set_property (GObject *object,
939 guint prop_id,
940 const GValue *value,
941 GParamSpec *pspec)
942{
943 GtkComboBox *combo_box = GTK_COMBO_BOX (object);
944 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
945
946 switch (prop_id)
947 {
948 case PROP_MODEL:
949 gtk_combo_box_set_model (combo_box, model: g_value_get_object (value));
950 break;
951
952 case PROP_ACTIVE:
953 gtk_combo_box_set_active (combo_box, index_: g_value_get_int (value));
954 break;
955
956 case PROP_HAS_FRAME:
957 if (priv->has_frame != g_value_get_boolean (value))
958 {
959 priv->has_frame = g_value_get_boolean (value);
960 if (priv->has_entry)
961 gtk_entry_set_has_frame (GTK_ENTRY (priv->child), setting: priv->has_frame);
962 g_object_notify (object, property_name: "has-frame");
963 }
964 break;
965
966 case PROP_POPUP_SHOWN:
967 if (g_value_get_boolean (value))
968 gtk_combo_box_popup (combo_box);
969 else
970 gtk_combo_box_popdown (combo_box);
971 break;
972
973 case PROP_BUTTON_SENSITIVITY:
974 gtk_combo_box_set_button_sensitivity (combo_box,
975 sensitivity: g_value_get_enum (value));
976 break;
977
978 case PROP_POPUP_FIXED_WIDTH:
979 gtk_combo_box_set_popup_fixed_width (combo_box,
980 fixed: g_value_get_boolean (value));
981 break;
982
983 case PROP_EDITING_CANCELED:
984 if (priv->editing_canceled != g_value_get_boolean (value))
985 {
986 priv->editing_canceled = g_value_get_boolean (value);
987 g_object_notify (object, property_name: "editing-canceled");
988 }
989 break;
990
991 case PROP_HAS_ENTRY:
992 priv->has_entry = g_value_get_boolean (value);
993 break;
994
995 case PROP_ENTRY_TEXT_COLUMN:
996 gtk_combo_box_set_entry_text_column (combo_box, text_column: g_value_get_int (value));
997 break;
998
999 case PROP_ID_COLUMN:
1000 gtk_combo_box_set_id_column (combo_box, id_column: g_value_get_int (value));
1001 break;
1002
1003 case PROP_ACTIVE_ID:
1004 gtk_combo_box_set_active_id (combo_box, active_id: g_value_get_string (value));
1005 break;
1006
1007 case PROP_CHILD:
1008 gtk_combo_box_set_child (combo_box, child: g_value_get_object (value));
1009 break;
1010
1011 default:
1012 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1013 break;
1014 }
1015}
1016
1017static void
1018gtk_combo_box_get_property (GObject *object,
1019 guint prop_id,
1020 GValue *value,
1021 GParamSpec *pspec)
1022{
1023 GtkComboBox *combo_box = GTK_COMBO_BOX (object);
1024 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1025
1026 switch (prop_id)
1027 {
1028 case PROP_MODEL:
1029 g_value_set_object (value, v_object: priv->model);
1030 break;
1031
1032 case PROP_ACTIVE:
1033 g_value_set_int (value, v_int: gtk_combo_box_get_active (combo_box));
1034 break;
1035
1036 case PROP_HAS_FRAME:
1037 g_value_set_boolean (value, v_boolean: priv->has_frame);
1038 break;
1039
1040 case PROP_POPUP_SHOWN:
1041 g_value_set_boolean (value, v_boolean: priv->popup_shown);
1042 break;
1043
1044 case PROP_BUTTON_SENSITIVITY:
1045 g_value_set_enum (value, v_enum: priv->button_sensitivity);
1046 break;
1047
1048 case PROP_POPUP_FIXED_WIDTH:
1049 g_value_set_boolean (value, v_boolean: priv->popup_fixed_width);
1050 break;
1051
1052 case PROP_EDITING_CANCELED:
1053 g_value_set_boolean (value, v_boolean: priv->editing_canceled);
1054 break;
1055
1056 case PROP_HAS_ENTRY:
1057 g_value_set_boolean (value, v_boolean: priv->has_entry);
1058 break;
1059
1060 case PROP_ENTRY_TEXT_COLUMN:
1061 g_value_set_int (value, v_int: priv->text_column);
1062 break;
1063
1064 case PROP_ID_COLUMN:
1065 g_value_set_int (value, v_int: priv->id_column);
1066 break;
1067
1068 case PROP_ACTIVE_ID:
1069 g_value_set_string (value, v_string: gtk_combo_box_get_active_id (combo_box));
1070 break;
1071
1072 case PROP_CHILD:
1073 g_value_set_object (value, v_object: gtk_combo_box_get_child (combo_box));
1074 break;
1075
1076 default:
1077 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1078 break;
1079 }
1080}
1081
1082static void
1083gtk_combo_box_button_toggled (GtkWidget *widget,
1084 gpointer data)
1085{
1086 GtkComboBox *combo_box = GTK_COMBO_BOX (data);
1087 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1088
1089 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
1090 {
1091 if (!priv->popup_in_progress)
1092 gtk_combo_box_popup (combo_box);
1093 }
1094 else
1095 {
1096 gtk_combo_box_popdown (combo_box);
1097 }
1098}
1099
1100static void
1101gtk_combo_box_create_child (GtkComboBox *combo_box)
1102{
1103 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1104
1105 if (priv->has_entry)
1106 {
1107 GtkWidget *entry;
1108
1109 entry = gtk_entry_new ();
1110 gtk_combo_box_set_child (combo_box, child: entry);
1111
1112 gtk_widget_add_css_class (GTK_WIDGET (entry), css_class: "combo");
1113
1114 g_signal_connect (combo_box, "changed",
1115 G_CALLBACK (gtk_combo_box_entry_active_changed), NULL);
1116 }
1117 else
1118 {
1119 priv->cell_view = gtk_cell_view_new_with_context (area: priv->area, NULL);
1120 gtk_widget_set_hexpand (widget: priv->cell_view, TRUE);
1121 gtk_cell_view_set_fit_model (GTK_CELL_VIEW (priv->cell_view), TRUE);
1122 gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), model: priv->model);
1123 gtk_box_insert_child_after (GTK_BOX (gtk_widget_get_parent (priv->arrow)), child: priv->cell_view, NULL);
1124 priv->child = priv->cell_view;
1125 }
1126}
1127
1128static void
1129gtk_combo_box_add (GtkComboBox *combo_box,
1130 GtkWidget *widget)
1131{
1132 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1133
1134 if (priv->box == NULL)
1135 {
1136 gtk_widget_set_parent (widget, GTK_WIDGET (combo_box));
1137 return;
1138 }
1139
1140 if (priv->has_entry && !GTK_IS_ENTRY (widget))
1141 {
1142 g_warning ("Attempting to add a widget with type %s to a GtkComboBox that needs an entry "
1143 "(need an instance of GtkEntry or of a subclass)",
1144 G_OBJECT_TYPE_NAME (widget));
1145 return;
1146 }
1147
1148 g_clear_pointer (&priv->cell_view, gtk_widget_unparent);
1149
1150 gtk_widget_set_hexpand (widget, TRUE);
1151 gtk_box_insert_child_after (GTK_BOX (priv->box), child: widget, NULL);
1152
1153 priv->child = widget;
1154
1155 if (priv->has_entry)
1156 {
1157 g_signal_connect (widget, "changed",
1158 G_CALLBACK (gtk_combo_box_entry_contents_changed),
1159 combo_box);
1160
1161 gtk_entry_set_has_frame (GTK_ENTRY (widget), setting: priv->has_frame);
1162 }
1163}
1164
1165static void
1166gtk_combo_box_remove (GtkComboBox *combo_box,
1167 GtkWidget *widget)
1168{
1169 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1170 GtkTreePath *path;
1171
1172 if (priv->has_entry)
1173 {
1174 if (widget && widget == priv->child)
1175 g_signal_handlers_disconnect_by_func (widget,
1176 gtk_combo_box_entry_contents_changed,
1177 combo_box);
1178 }
1179
1180 gtk_box_remove (GTK_BOX (priv->box), child: widget);
1181
1182 priv->child = NULL;
1183
1184 if (gtk_widget_in_destruction (GTK_WIDGET (combo_box)))
1185 return;
1186
1187 gtk_widget_queue_resize (GTK_WIDGET (combo_box));
1188
1189 gtk_combo_box_create_child (combo_box);
1190
1191 if (gtk_tree_row_reference_valid (reference: priv->active_row))
1192 {
1193 path = gtk_tree_row_reference_get_path (reference: priv->active_row);
1194 gtk_combo_box_set_active_internal (combo_box, path);
1195 gtk_tree_path_free (path);
1196 }
1197 else
1198 {
1199 gtk_combo_box_set_active_internal (combo_box, NULL);
1200 }
1201}
1202
1203static void
1204gtk_combo_box_menu_show (GtkWidget *menu,
1205 gpointer user_data)
1206{
1207 GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
1208 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1209
1210 gtk_combo_box_child_show (widget: menu, combo_box: user_data);
1211
1212 priv->popup_in_progress = TRUE;
1213 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button),
1214 TRUE);
1215 priv->popup_in_progress = FALSE;
1216}
1217
1218static void
1219gtk_combo_box_menu_hide (GtkWidget *menu,
1220 gpointer user_data)
1221{
1222 GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
1223 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1224
1225 gtk_combo_box_child_hide (widget: menu,combo_box: user_data);
1226
1227 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
1228}
1229
1230static gboolean
1231cell_layout_is_sensitive (GtkCellLayout *layout)
1232{
1233 GList *cells, *list;
1234 gboolean sensitive;
1235
1236 cells = gtk_cell_layout_get_cells (cell_layout: layout);
1237
1238 sensitive = FALSE;
1239 for (list = cells; list; list = list->next)
1240 {
1241 g_object_get (object: list->data, first_property_name: "sensitive", &sensitive, NULL);
1242
1243 if (sensitive)
1244 break;
1245 }
1246 g_list_free (list: cells);
1247
1248 return sensitive;
1249}
1250
1251static gboolean
1252tree_column_row_is_sensitive (GtkComboBox *combo_box,
1253 GtkTreeIter *iter)
1254{
1255 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1256
1257 if (priv->row_separator_func)
1258 {
1259 if (priv->row_separator_func (priv->model, iter,
1260 priv->row_separator_data))
1261 return FALSE;
1262 }
1263
1264 gtk_cell_area_apply_attributes (area: priv->area, tree_model: priv->model, iter, FALSE, FALSE);
1265 return cell_layout_is_sensitive (GTK_CELL_LAYOUT (priv->area));
1266}
1267
1268static void
1269gtk_combo_box_menu_popup (GtkComboBox *combo_box)
1270{
1271 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1272#if 0
1273 int active_item;
1274 GtkWidget *active;
1275 int width, min_width, nat_width;
1276#endif
1277
1278 gtk_tree_popover_open_submenu (popover: GTK_TREE_POPOVER (ptr: priv->popup_widget), name: "main");
1279 gtk_popover_popup (GTK_POPOVER (priv->popup_widget));
1280
1281#if 0
1282 active_item = -1;
1283 if (gtk_tree_row_reference_valid (priv->active_row))
1284 {
1285 GtkTreePath *path;
1286
1287 path = gtk_tree_row_reference_get_path (priv->active_row);
1288 active_item = gtk_tree_path_get_indices (path)[0];
1289 gtk_tree_path_free (path);
1290 }
1291
1292 /* FIXME handle nested menus better */
1293 //gtk_tree_popover_set_active (GTK_TREE_POPOVER (priv->popup_widget), active_item);
1294
1295 width = gtk_widget_get_width (GTK_WIDGET (combo_box));
1296 gtk_widget_set_size_request (priv->popup_widget, -1, -1);
1297 gtk_widget_measure (priv->popup_widget, GTK_ORIENTATION_HORIZONTAL, -1,
1298 &min_width, &nat_width, NULL, NULL);
1299
1300 if (priv->popup_fixed_width)
1301 width = MAX (width, min_width);
1302 else
1303 width = MAX (width, nat_width);
1304
1305 gtk_widget_set_size_request (priv->popup_widget, width, -1);
1306
1307 g_signal_handlers_disconnect_by_func (priv->popup_widget,
1308 gtk_menu_update_scroll_offset,
1309 NULL);
1310
1311 if (priv->cell_view == NULL)
1312 {
1313 g_object_set (priv->popup_widget,
1314 "anchor-hints", (GDK_ANCHOR_FLIP_Y |
1315 GDK_ANCHOR_SLIDE |
1316 GDK_ANCHOR_RESIZE),
1317 "rect-anchor-dx", 0,
1318 NULL);
1319
1320 gtk_menu_popup_at_widget (GTK_MENU (priv->popup_widget),
1321 gtk_bin_get_child (GTK_BIN (combo_box)),
1322 GDK_GRAVITY_SOUTH_WEST,
1323 GDK_GRAVITY_NORTH_WEST,
1324 NULL);
1325 }
1326 else
1327 {
1328 int rect_anchor_dy = -2;
1329 GList *i;
1330 GtkWidget *child;
1331
1332 /* FIXME handle nested menus better */
1333 active = gtk_menu_get_active (GTK_MENU (priv->popup_widget));
1334
1335 if (!(active && gtk_widget_get_visible (active)))
1336 {
1337 GList *children;
1338 children = gtk_menu_shell_get_items (GTK_MENU_SHELL (priv->popup_widget));
1339 for (i = children; i && !active; i = i->next)
1340 {
1341 child = i->data;
1342
1343 if (child && gtk_widget_get_visible (child))
1344 active = child;
1345 }
1346 g_list_free (children);
1347 }
1348
1349 if (active)
1350 {
1351 int child_height;
1352 GList *children;
1353 children = gtk_menu_shell_get_items (GTK_MENU_SHELL (priv->popup_widget));
1354 for (i = children; i && i->data != active; i = i->next)
1355 {
1356 child = i->data;
1357
1358 if (child && gtk_widget_get_visible (child))
1359 {
1360 gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, -1,
1361 &child_height, NULL, NULL, NULL);
1362 rect_anchor_dy -= child_height;
1363 }
1364 }
1365 g_list_free (children);
1366
1367 gtk_widget_measure (active, GTK_ORIENTATION_VERTICAL, -1,
1368 &child_height, NULL, NULL, NULL);
1369 rect_anchor_dy -= child_height / 2;
1370 }
1371
1372 g_object_set (priv->popup_widget,
1373 "anchor-hints", (GDK_ANCHOR_SLIDE |
1374 GDK_ANCHOR_RESIZE),
1375 "rect-anchor-dy", rect_anchor_dy,
1376 NULL);
1377
1378 g_signal_connect (priv->popup_widget,
1379 "popped-up",
1380 G_CALLBACK (gtk_menu_update_scroll_offset),
1381 NULL);
1382
1383 gtk_menu_popup_at_widget (GTK_MENU (priv->popup_widget),
1384 GTK_WIDGET (combo_box),
1385 GDK_GRAVITY_WEST,
1386 GDK_GRAVITY_NORTH_WEST,
1387 NULL);
1388 }
1389#endif
1390}
1391
1392/**
1393 * gtk_combo_box_popup:
1394 * @combo_box: a `GtkComboBox`
1395 *
1396 * Pops up the menu or dropdown list of @combo_box.
1397 *
1398 * This function is mostly intended for use by accessibility technologies;
1399 * applications should have little use for it.
1400 *
1401 * Before calling this, @combo_box must be mapped, or nothing will happen.
1402 */
1403void
1404gtk_combo_box_popup (GtkComboBox *combo_box)
1405{
1406 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
1407
1408 if (gtk_widget_get_mapped (GTK_WIDGET (combo_box)))
1409 g_signal_emit (instance: combo_box, signal_id: combo_box_signals[POPUP], detail: 0);
1410}
1411
1412/**
1413 * gtk_combo_box_popup_for_device:
1414 * @combo_box: a `GtkComboBox`
1415 * @device: a `GdkDevice`
1416 *
1417 * Pops up the menu of @combo_box.
1418 *
1419 * Note that currently this does not do anything with the device, as it was
1420 * previously only used for list-mode combo boxes, and those were removed
1421 * in GTK 4. However, it is retained in case similar functionality is added
1422 * back later.
1423 */
1424void
1425gtk_combo_box_popup_for_device (GtkComboBox *combo_box,
1426 GdkDevice *device)
1427{
1428 /* As above, this currently does not do anything useful, and nothing with the
1429 * passed-in device. But the bits that are not blatantly obsolete are kept. */
1430 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1431
1432 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
1433 g_return_if_fail (GDK_IS_DEVICE (device));
1434
1435 if (!gtk_widget_get_realized (GTK_WIDGET (combo_box)))
1436 return;
1437
1438 if (gtk_widget_get_mapped (widget: priv->popup_widget))
1439 return;
1440
1441 gtk_combo_box_menu_popup (combo_box);
1442}
1443
1444static void
1445gtk_combo_box_real_popup (GtkComboBox *combo_box)
1446{
1447 gtk_combo_box_menu_popup (combo_box);
1448}
1449
1450static gboolean
1451gtk_combo_box_real_popdown (GtkComboBox *combo_box)
1452{
1453 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1454
1455 if (priv->popup_shown)
1456 {
1457 gtk_combo_box_popdown (combo_box);
1458 return TRUE;
1459 }
1460
1461 return FALSE;
1462}
1463
1464/**
1465 * gtk_combo_box_popdown:
1466 * @combo_box: a `GtkComboBox`
1467 *
1468 * Hides the menu or dropdown list of @combo_box.
1469 *
1470 * This function is mostly intended for use by accessibility technologies;
1471 * applications should have little use for it.
1472 */
1473void
1474gtk_combo_box_popdown (GtkComboBox *combo_box)
1475{
1476 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1477
1478 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
1479
1480 gtk_popover_popdown (GTK_POPOVER (priv->popup_widget));
1481}
1482
1483static void
1484gtk_combo_box_unset_model (GtkComboBox *combo_box)
1485{
1486 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1487
1488 if (priv->model)
1489 {
1490 g_signal_handlers_disconnect_by_func (priv->model,
1491 gtk_combo_box_model_row_inserted,
1492 combo_box);
1493 g_signal_handlers_disconnect_by_func (priv->model,
1494 gtk_combo_box_model_row_deleted,
1495 combo_box);
1496 g_signal_handlers_disconnect_by_func (priv->model,
1497 gtk_combo_box_model_rows_reordered,
1498 combo_box);
1499 g_signal_handlers_disconnect_by_func (priv->model,
1500 gtk_combo_box_model_row_changed,
1501 combo_box);
1502
1503 g_object_unref (object: priv->model);
1504 priv->model = NULL;
1505 }
1506
1507 if (priv->active_row)
1508 {
1509 gtk_tree_row_reference_free (reference: priv->active_row);
1510 priv->active_row = NULL;
1511 }
1512
1513 if (priv->cell_view)
1514 gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), NULL);
1515}
1516
1517static void
1518gtk_combo_box_child_show (GtkWidget *widget,
1519 GtkComboBox *combo_box)
1520{
1521 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1522
1523 priv->popup_shown = TRUE;
1524 g_object_notify (G_OBJECT (combo_box), property_name: "popup-shown");
1525}
1526
1527static void
1528gtk_combo_box_child_hide (GtkWidget *widget,
1529 GtkComboBox *combo_box)
1530{
1531 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1532
1533 priv->popup_shown = FALSE;
1534 g_object_notify (G_OBJECT (combo_box), property_name: "popup-shown");
1535}
1536
1537typedef struct {
1538 GtkComboBox *combo;
1539 GtkTreePath *path;
1540 GtkTreeIter iter;
1541 gboolean found;
1542 gboolean set;
1543} SearchData;
1544
1545static gboolean
1546tree_next_func (GtkTreeModel *model,
1547 GtkTreePath *path,
1548 GtkTreeIter *iter,
1549 gpointer data)
1550{
1551 SearchData *search_data = (SearchData *)data;
1552
1553 if (search_data->found)
1554 {
1555 if (!tree_column_row_is_sensitive (combo_box: search_data->combo, iter))
1556 return FALSE;
1557
1558 search_data->set = TRUE;
1559 search_data->iter = *iter;
1560
1561 return TRUE;
1562 }
1563
1564 if (gtk_tree_path_compare (a: path, b: search_data->path) == 0)
1565 search_data->found = TRUE;
1566
1567 return FALSE;
1568}
1569
1570static gboolean
1571tree_next (GtkComboBox *combo,
1572 GtkTreeModel *model,
1573 GtkTreeIter *iter,
1574 GtkTreeIter *next)
1575{
1576 SearchData search_data;
1577
1578 search_data.combo = combo;
1579 search_data.path = gtk_tree_model_get_path (tree_model: model, iter);
1580 search_data.found = FALSE;
1581 search_data.set = FALSE;
1582
1583 gtk_tree_model_foreach (model, func: tree_next_func, user_data: &search_data);
1584
1585 *next = search_data.iter;
1586
1587 gtk_tree_path_free (path: search_data.path);
1588
1589 return search_data.set;
1590}
1591
1592static gboolean
1593tree_prev_func (GtkTreeModel *model,
1594 GtkTreePath *path,
1595 GtkTreeIter *iter,
1596 gpointer data)
1597{
1598 SearchData *search_data = (SearchData *)data;
1599
1600 if (gtk_tree_path_compare (a: path, b: search_data->path) == 0)
1601 {
1602 search_data->found = TRUE;
1603 return TRUE;
1604 }
1605
1606 if (!tree_column_row_is_sensitive (combo_box: search_data->combo, iter))
1607 return FALSE;
1608
1609 search_data->set = TRUE;
1610 search_data->iter = *iter;
1611
1612 return FALSE;
1613}
1614
1615static gboolean
1616tree_prev (GtkComboBox *combo,
1617 GtkTreeModel *model,
1618 GtkTreeIter *iter,
1619 GtkTreeIter *prev)
1620{
1621 SearchData search_data;
1622
1623 search_data.combo = combo;
1624 search_data.path = gtk_tree_model_get_path (tree_model: model, iter);
1625 search_data.found = FALSE;
1626 search_data.set = FALSE;
1627
1628 gtk_tree_model_foreach (model, func: tree_prev_func, user_data: &search_data);
1629
1630 *prev = search_data.iter;
1631
1632 gtk_tree_path_free (path: search_data.path);
1633
1634 return search_data.set;
1635}
1636
1637static gboolean
1638tree_last_func (GtkTreeModel *model,
1639 GtkTreePath *path,
1640 GtkTreeIter *iter,
1641 gpointer data)
1642{
1643 SearchData *search_data = (SearchData *)data;
1644
1645 if (!tree_column_row_is_sensitive (combo_box: search_data->combo, iter))
1646 return FALSE;
1647
1648 search_data->set = TRUE;
1649 search_data->iter = *iter;
1650
1651 return FALSE;
1652}
1653
1654static gboolean
1655tree_last (GtkComboBox *combo,
1656 GtkTreeModel *model,
1657 GtkTreeIter *last)
1658{
1659 SearchData search_data;
1660
1661 search_data.combo = combo;
1662 search_data.set = FALSE;
1663
1664 gtk_tree_model_foreach (model, func: tree_last_func, user_data: &search_data);
1665
1666 *last = search_data.iter;
1667
1668 return search_data.set;
1669}
1670
1671
1672static gboolean
1673tree_first_func (GtkTreeModel *model,
1674 GtkTreePath *path,
1675 GtkTreeIter *iter,
1676 gpointer data)
1677{
1678 SearchData *search_data = (SearchData *)data;
1679
1680 if (!tree_column_row_is_sensitive (combo_box: search_data->combo, iter))
1681 return FALSE;
1682
1683 search_data->set = TRUE;
1684 search_data->iter = *iter;
1685
1686 return TRUE;
1687}
1688
1689static gboolean
1690tree_first (GtkComboBox *combo,
1691 GtkTreeModel *model,
1692 GtkTreeIter *first)
1693{
1694 SearchData search_data;
1695
1696 search_data.combo = combo;
1697 search_data.set = FALSE;
1698
1699 gtk_tree_model_foreach (model, func: tree_first_func, user_data: &search_data);
1700
1701 *first = search_data.iter;
1702
1703 return search_data.set;
1704}
1705
1706static gboolean
1707gtk_combo_box_scroll_controller_scroll (GtkEventControllerScroll *scroll,
1708 double dx,
1709 double dy,
1710 GtkComboBox *combo_box)
1711{
1712 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1713 gboolean found = FALSE;
1714 GtkTreeIter iter;
1715 GtkTreeIter new_iter;
1716
1717 if (!gtk_combo_box_get_active_iter (combo_box, iter: &iter))
1718 return GDK_EVENT_PROPAGATE;
1719
1720 if (dy < 0)
1721 found = tree_prev (combo: combo_box, model: priv->model, iter: &iter, prev: &new_iter);
1722 else if (dy > 0)
1723 found = tree_next (combo: combo_box, model: priv->model, iter: &iter, next: &new_iter);
1724
1725 if (found)
1726 gtk_combo_box_set_active_iter (combo_box, iter: &new_iter);
1727
1728 return found;
1729
1730}
1731
1732/* callbacks */
1733static void
1734gtk_combo_box_menu_activate (GtkWidget *menu,
1735 const char *path,
1736 GtkComboBox *combo_box)
1737{
1738 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1739 GtkTreeIter iter;
1740
1741 if (gtk_tree_model_get_iter_from_string (tree_model: priv->model, iter: &iter, path_string: path))
1742 gtk_combo_box_set_active_iter (combo_box, iter: &iter);
1743
1744 g_object_set (object: combo_box,
1745 first_property_name: "editing-canceled", FALSE,
1746 NULL);
1747}
1748
1749
1750static void
1751gtk_combo_box_update_sensitivity (GtkComboBox *combo_box)
1752{
1753 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1754 GtkTreeIter iter;
1755 gboolean sensitive = TRUE; /* fool code checkers */
1756
1757 if (!priv->button)
1758 return;
1759
1760 switch (priv->button_sensitivity)
1761 {
1762 case GTK_SENSITIVITY_ON:
1763 sensitive = TRUE;
1764 break;
1765 case GTK_SENSITIVITY_OFF:
1766 sensitive = FALSE;
1767 break;
1768 case GTK_SENSITIVITY_AUTO:
1769 sensitive = priv->model &&
1770 gtk_tree_model_get_iter_first (tree_model: priv->model, iter: &iter);
1771 break;
1772 default:
1773 g_assert_not_reached ();
1774 break;
1775 }
1776
1777 gtk_widget_set_sensitive (widget: priv->button, sensitive);
1778}
1779
1780static void
1781gtk_combo_box_model_row_inserted (GtkTreeModel *model,
1782 GtkTreePath *path,
1783 GtkTreeIter *iter,
1784 gpointer user_data)
1785{
1786 GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
1787
1788 gtk_combo_box_update_sensitivity (combo_box);
1789}
1790
1791static void
1792gtk_combo_box_model_row_deleted (GtkTreeModel *model,
1793 GtkTreePath *path,
1794 gpointer user_data)
1795{
1796 GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
1797 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1798
1799 if (!gtk_tree_row_reference_valid (reference: priv->active_row))
1800 {
1801 if (priv->cell_view)
1802 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL);
1803 g_signal_emit (instance: combo_box, signal_id: combo_box_signals[CHANGED], detail: 0);
1804 }
1805
1806 gtk_combo_box_update_sensitivity (combo_box);
1807}
1808
1809static void
1810gtk_combo_box_model_rows_reordered (GtkTreeModel *model,
1811 GtkTreePath *path,
1812 GtkTreeIter *iter,
1813 int *new_order,
1814 gpointer user_data)
1815{
1816 gtk_tree_row_reference_reordered (G_OBJECT (user_data), path, iter, new_order);
1817}
1818
1819static void
1820gtk_combo_box_model_row_changed (GtkTreeModel *model,
1821 GtkTreePath *path,
1822 GtkTreeIter *iter,
1823 gpointer user_data)
1824{
1825 GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
1826 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1827 GtkTreePath *active_path;
1828
1829 /* FIXME this belongs to GtkCellView */
1830 if (gtk_tree_row_reference_valid (reference: priv->active_row))
1831 {
1832 active_path = gtk_tree_row_reference_get_path (reference: priv->active_row);
1833 if (gtk_tree_path_compare (a: path, b: active_path) == 0 &&
1834 priv->cell_view)
1835 gtk_widget_queue_resize (GTK_WIDGET (priv->cell_view));
1836 gtk_tree_path_free (path: active_path);
1837 }
1838}
1839
1840static gboolean
1841gtk_combo_box_menu_key (GtkEventControllerKey *key,
1842 guint keyval,
1843 guint keycode,
1844 GdkModifierType modifiers,
1845 GtkComboBox *combo_box)
1846{
1847 gtk_event_controller_key_forward (controller: key, GTK_WIDGET (combo_box));
1848
1849 return TRUE;
1850}
1851
1852/*
1853 * GtkCellLayout implementation
1854 */
1855static GtkCellArea *
1856gtk_combo_box_cell_layout_get_area (GtkCellLayout *cell_layout)
1857{
1858 GtkComboBox *combo_box = GTK_COMBO_BOX (cell_layout);
1859 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1860
1861 return priv->area;
1862}
1863
1864/*
1865 * public API
1866 */
1867
1868/**
1869 * gtk_combo_box_new:
1870 *
1871 * Creates a new empty `GtkComboBox`.
1872 *
1873 * Returns: A new `GtkComboBox`
1874 */
1875GtkWidget *
1876gtk_combo_box_new (void)
1877{
1878 return g_object_new (GTK_TYPE_COMBO_BOX, NULL);
1879}
1880
1881/**
1882 * gtk_combo_box_new_with_entry:
1883 *
1884 * Creates a new empty `GtkComboBox` with an entry.
1885 *
1886 * In order to use a combo box with entry, you need to tell it
1887 * which column of the model contains the text for the entry
1888 * by calling [method@Gtk.ComboBox.set_entry_text_column].
1889 *
1890 * Returns: A new `GtkComboBox`
1891 */
1892GtkWidget *
1893gtk_combo_box_new_with_entry (void)
1894{
1895 return g_object_new (GTK_TYPE_COMBO_BOX, first_property_name: "has-entry", TRUE, NULL);
1896}
1897
1898/**
1899 * gtk_combo_box_new_with_model:
1900 * @model: a `GtkTreeModel`
1901 *
1902 * Creates a new `GtkComboBox` with a model.
1903 *
1904 * Returns: A new `GtkComboBox`
1905 */
1906GtkWidget *
1907gtk_combo_box_new_with_model (GtkTreeModel *model)
1908{
1909 GtkComboBox *combo_box;
1910
1911 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
1912
1913 combo_box = g_object_new (GTK_TYPE_COMBO_BOX, first_property_name: "model", model, NULL);
1914
1915 return GTK_WIDGET (combo_box);
1916}
1917
1918/**
1919 * gtk_combo_box_new_with_model_and_entry:
1920 * @model: A `GtkTreeModel`
1921 *
1922 * Creates a new empty `GtkComboBox` with an entry and a model.
1923 *
1924 * See also [ctor@Gtk.ComboBox.new_with_entry].
1925 *
1926 * Returns: A new `GtkComboBox`
1927 */
1928GtkWidget *
1929gtk_combo_box_new_with_model_and_entry (GtkTreeModel *model)
1930{
1931 return g_object_new (GTK_TYPE_COMBO_BOX,
1932 first_property_name: "has-entry", TRUE,
1933 "model", model,
1934 NULL);
1935}
1936
1937/**
1938 * gtk_combo_box_get_active:
1939 * @combo_box: A `GtkComboBox`
1940 *
1941 * Returns the index of the currently active item.
1942 *
1943 * If the model is a non-flat treemodel, and the active item is not
1944 * an immediate child of the root of the tree, this function returns
1945 * `gtk_tree_path_get_indices (path)[0]`, where `path` is the
1946 * [struct@Gtk.TreePath] of the active item.
1947 *
1948 * Returns: An integer which is the index of the currently active item,
1949 * or -1 if there’s no active item
1950 */
1951int
1952gtk_combo_box_get_active (GtkComboBox *combo_box)
1953{
1954 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1955 int result;
1956
1957 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0);
1958
1959 if (gtk_tree_row_reference_valid (reference: priv->active_row))
1960 {
1961 GtkTreePath *path;
1962
1963 path = gtk_tree_row_reference_get_path (reference: priv->active_row);
1964 result = gtk_tree_path_get_indices (path)[0];
1965 gtk_tree_path_free (path);
1966 }
1967 else
1968 result = -1;
1969
1970 return result;
1971}
1972
1973/**
1974 * gtk_combo_box_set_active: (attributes org.gtk.Method.set_property=active)
1975 * @combo_box: a `GtkComboBox`
1976 * @index_: An index in the model passed during construction,
1977 * or -1 to have no active item
1978 *
1979 * Sets the active item of @combo_box to be the item at @index.
1980 */
1981void
1982gtk_combo_box_set_active (GtkComboBox *combo_box,
1983 int index_)
1984{
1985 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
1986 GtkTreePath *path = NULL;
1987
1988 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
1989 g_return_if_fail (index_ >= -1);
1990
1991 if (priv->model == NULL)
1992 {
1993 /* Save index, in case the model is set after the index */
1994 priv->active = index_;
1995 if (index_ != -1)
1996 return;
1997 }
1998
1999 if (index_ != -1)
2000 path = gtk_tree_path_new_from_indices (first_index: index_, -1);
2001
2002 gtk_combo_box_set_active_internal (combo_box, path);
2003
2004 if (path)
2005 gtk_tree_path_free (path);
2006}
2007
2008static void
2009gtk_combo_box_set_active_internal (GtkComboBox *combo_box,
2010 GtkTreePath *path)
2011{
2012 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2013 GtkTreePath *active_path;
2014 int path_cmp;
2015
2016 /* Remember whether the initially active row is valid. */
2017 gboolean is_valid_row_reference = gtk_tree_row_reference_valid (reference: priv->active_row);
2018
2019 if (path && is_valid_row_reference)
2020 {
2021 active_path = gtk_tree_row_reference_get_path (reference: priv->active_row);
2022 path_cmp = gtk_tree_path_compare (a: path, b: active_path);
2023 gtk_tree_path_free (path: active_path);
2024 if (path_cmp == 0)
2025 return;
2026 }
2027
2028 if (priv->active_row)
2029 {
2030 gtk_tree_row_reference_free (reference: priv->active_row);
2031 priv->active_row = NULL;
2032 }
2033
2034 if (!path)
2035 {
2036 gtk_tree_popover_set_active (popover: GTK_TREE_POPOVER (ptr: priv->popup_widget), item: -1);
2037
2038 if (priv->cell_view)
2039 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL);
2040
2041 /*
2042 * Do not emit a "changed" signal when an already invalid selection was
2043 * now set to invalid.
2044 */
2045 if (!is_valid_row_reference)
2046 return;
2047 }
2048 else
2049 {
2050 priv->active_row =
2051 gtk_tree_row_reference_new (model: priv->model, path);
2052
2053 gtk_tree_popover_set_active (popover: GTK_TREE_POPOVER (ptr: priv->popup_widget),
2054 item: gtk_tree_path_get_indices (path)[0]);
2055
2056 if (priv->cell_view)
2057 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), path);
2058 }
2059
2060 g_signal_emit (instance: combo_box, signal_id: combo_box_signals[CHANGED], detail: 0);
2061 g_object_notify (G_OBJECT (combo_box), property_name: "active");
2062 if (priv->id_column >= 0)
2063 g_object_notify (G_OBJECT (combo_box), property_name: "active-id");
2064}
2065
2066
2067/**
2068 * gtk_combo_box_get_active_iter:
2069 * @combo_box: A `GtkComboBox`
2070 * @iter: (out): A `GtkTreeIter`
2071 *
2072 * Sets @iter to point to the currently active item.
2073 *
2074 * If no item is active, @iter is left unchanged.
2075 *
2076 * Returns: %TRUE if @iter was set, %FALSE otherwise
2077 */
2078gboolean
2079gtk_combo_box_get_active_iter (GtkComboBox *combo_box,
2080 GtkTreeIter *iter)
2081{
2082 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2083 GtkTreePath *path;
2084 gboolean result;
2085
2086 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE);
2087
2088 if (!gtk_tree_row_reference_valid (reference: priv->active_row))
2089 return FALSE;
2090
2091 path = gtk_tree_row_reference_get_path (reference: priv->active_row);
2092 result = gtk_tree_model_get_iter (tree_model: priv->model, iter, path);
2093 gtk_tree_path_free (path);
2094
2095 return result;
2096}
2097
2098/**
2099 * gtk_combo_box_set_active_iter:
2100 * @combo_box: A `GtkComboBox`
2101 * @iter: (nullable): The `GtkTreeIter`
2102 *
2103 * Sets the current active item to be the one referenced by @iter.
2104 *
2105 * If @iter is %NULL, the active item is unset.
2106 */
2107void
2108gtk_combo_box_set_active_iter (GtkComboBox *combo_box,
2109 GtkTreeIter *iter)
2110{
2111 GtkTreePath *path = NULL;
2112
2113 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
2114
2115 if (iter)
2116 path = gtk_tree_model_get_path (tree_model: gtk_combo_box_get_model (combo_box), iter);
2117
2118 gtk_combo_box_set_active_internal (combo_box, path);
2119 gtk_tree_path_free (path);
2120}
2121
2122/**
2123 * gtk_combo_box_set_model: (attributes org.gtk.Method.set_property=model)
2124 * @combo_box: A `GtkComboBox`
2125 * @model: (nullable): A `GtkTreeModel`
2126 *
2127 * Sets the model used by @combo_box to be @model.
2128 *
2129 * Will unset a previously set model (if applicable). If model is %NULL,
2130 * then it will unset the model.
2131 *
2132 * Note that this function does not clear the cell renderers, you have to
2133 * call [method@Gtk.CellLayout.clear] yourself if you need to set up different
2134 * cell renderers for the new model.
2135 */
2136void
2137gtk_combo_box_set_model (GtkComboBox *combo_box,
2138 GtkTreeModel *model)
2139{
2140 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2141
2142 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
2143 g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
2144
2145 if (model == priv->model)
2146 return;
2147
2148 gtk_combo_box_unset_model (combo_box);
2149
2150 if (model == NULL)
2151 goto out;
2152
2153 priv->model = model;
2154 g_object_ref (priv->model);
2155
2156 g_signal_connect (priv->model, "row-inserted",
2157 G_CALLBACK (gtk_combo_box_model_row_inserted),
2158 combo_box);
2159 g_signal_connect (priv->model, "row-deleted",
2160 G_CALLBACK (gtk_combo_box_model_row_deleted),
2161 combo_box);
2162 g_signal_connect (priv->model, "rows-reordered",
2163 G_CALLBACK (gtk_combo_box_model_rows_reordered),
2164 combo_box);
2165 g_signal_connect (priv->model, "row-changed",
2166 G_CALLBACK (gtk_combo_box_model_row_changed),
2167 combo_box);
2168
2169 gtk_tree_popover_set_model (popover: GTK_TREE_POPOVER (ptr: priv->popup_widget), model: priv->model);
2170
2171 if (priv->cell_view)
2172 gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view),
2173 model: priv->model);
2174
2175 if (priv->active != -1)
2176 {
2177 /* If an index was set in advance, apply it now */
2178 gtk_combo_box_set_active (combo_box, index_: priv->active);
2179 priv->active = -1;
2180 }
2181
2182out:
2183 gtk_combo_box_update_sensitivity (combo_box);
2184
2185 g_object_notify (G_OBJECT (combo_box), property_name: "model");
2186}
2187
2188/**
2189 * gtk_combo_box_get_model: (attributes org.gtk.Method.get_property=model)
2190 * @combo_box: A `GtkComboBox`
2191 *
2192 * Returns the `GtkTreeModel` of @combo_box.
2193 *
2194 * Returns: (nullable) (transfer none): A `GtkTreeModel` which was passed
2195 * during construction.
2196 */
2197GtkTreeModel *
2198gtk_combo_box_get_model (GtkComboBox *combo_box)
2199{
2200 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2201
2202 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL);
2203
2204 return priv->model;
2205}
2206
2207static void
2208gtk_combo_box_real_move_active (GtkComboBox *combo_box,
2209 GtkScrollType scroll)
2210{
2211 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2212 GtkTreeIter iter;
2213 GtkTreeIter new_iter;
2214 gboolean active_iter;
2215 gboolean found;
2216
2217 if (!priv->model)
2218 {
2219 gtk_widget_error_bell (GTK_WIDGET (combo_box));
2220 return;
2221 }
2222
2223 active_iter = gtk_combo_box_get_active_iter (combo_box, iter: &iter);
2224
2225 switch (scroll)
2226 {
2227 case GTK_SCROLL_STEP_BACKWARD:
2228 case GTK_SCROLL_STEP_UP:
2229 case GTK_SCROLL_STEP_LEFT:
2230 if (active_iter)
2231 {
2232 found = tree_prev (combo: combo_box, model: priv->model,
2233 iter: &iter, prev: &new_iter);
2234 break;
2235 }
2236 G_GNUC_FALLTHROUGH;
2237
2238 case GTK_SCROLL_PAGE_FORWARD:
2239 case GTK_SCROLL_PAGE_DOWN:
2240 case GTK_SCROLL_PAGE_RIGHT:
2241 case GTK_SCROLL_END:
2242 found = tree_last (combo: combo_box, model: priv->model, last: &new_iter);
2243 break;
2244
2245 case GTK_SCROLL_STEP_FORWARD:
2246 case GTK_SCROLL_STEP_DOWN:
2247 case GTK_SCROLL_STEP_RIGHT:
2248 if (active_iter)
2249 {
2250 found = tree_next (combo: combo_box, model: priv->model,
2251 iter: &iter, next: &new_iter);
2252 break;
2253 }
2254 G_GNUC_FALLTHROUGH;
2255
2256 case GTK_SCROLL_PAGE_BACKWARD:
2257 case GTK_SCROLL_PAGE_UP:
2258 case GTK_SCROLL_PAGE_LEFT:
2259 case GTK_SCROLL_START:
2260 found = tree_first (combo: combo_box, model: priv->model, first: &new_iter);
2261 break;
2262
2263 case GTK_SCROLL_NONE:
2264 case GTK_SCROLL_JUMP:
2265 default:
2266 return;
2267 }
2268
2269 if (found && active_iter)
2270 {
2271 GtkTreePath *old_path;
2272 GtkTreePath *new_path;
2273
2274 old_path = gtk_tree_model_get_path (tree_model: priv->model, iter: &iter);
2275 new_path = gtk_tree_model_get_path (tree_model: priv->model, iter: &new_iter);
2276
2277 if (gtk_tree_path_compare (a: old_path, b: new_path) == 0)
2278 found = FALSE;
2279
2280 gtk_tree_path_free (path: old_path);
2281 gtk_tree_path_free (path: new_path);
2282 }
2283
2284 if (found)
2285 {
2286 gtk_combo_box_set_active_iter (combo_box, iter: &new_iter);
2287 }
2288 else
2289 {
2290 gtk_widget_error_bell (GTK_WIDGET (combo_box));
2291 }
2292}
2293
2294static gboolean
2295gtk_combo_box_mnemonic_activate (GtkWidget *widget,
2296 gboolean group_cycling)
2297{
2298 GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
2299 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2300
2301 if (priv->has_entry)
2302 {
2303 if (priv->child)
2304 gtk_widget_grab_focus (widget: priv->child);
2305 }
2306 else
2307 gtk_widget_mnemonic_activate (widget: priv->button, group_cycling);
2308
2309 return TRUE;
2310}
2311
2312static gboolean
2313gtk_combo_box_grab_focus (GtkWidget *widget)
2314{
2315 GtkComboBox *combo_box = GTK_COMBO_BOX (widget);
2316 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2317
2318 if (priv->has_entry)
2319 {
2320 if (priv->child)
2321 return gtk_widget_grab_focus (widget: priv->child);
2322 else
2323 return FALSE;
2324 }
2325 else
2326 return gtk_widget_grab_focus (widget: priv->button);
2327}
2328
2329static void
2330gtk_combo_box_unmap (GtkWidget *widget)
2331{
2332 gtk_combo_box_popdown (GTK_COMBO_BOX (widget));
2333
2334 GTK_WIDGET_CLASS (gtk_combo_box_parent_class)->unmap (widget);
2335}
2336
2337static void
2338gtk_combo_box_entry_contents_changed (GtkEntry *entry,
2339 gpointer user_data)
2340{
2341 GtkComboBox *combo_box = GTK_COMBO_BOX (user_data);
2342
2343 /*
2344 * Fixes regression reported in bug #574059. The old functionality relied on
2345 * bug #572478. As a bugfix, we now emit the "changed" signal ourselves
2346 * when the selection was already set to -1.
2347 */
2348 if (gtk_combo_box_get_active(combo_box) == -1)
2349 g_signal_emit_by_name (instance: combo_box, detailed_signal: "changed");
2350 else
2351 gtk_combo_box_set_active (combo_box, index_: -1);
2352}
2353
2354static void
2355gtk_combo_box_entry_active_changed (GtkComboBox *combo_box,
2356 gpointer user_data)
2357{
2358 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2359 GtkTreeModel *model;
2360 GtkTreeIter iter;
2361
2362 if (gtk_combo_box_get_active_iter (combo_box, iter: &iter))
2363 {
2364 GtkEntry *entry = GTK_ENTRY (priv->child);
2365
2366 if (entry)
2367 {
2368 GtkTreePath *path;
2369 char *path_str;
2370 char *text = NULL;
2371
2372 model = gtk_combo_box_get_model (combo_box);
2373 path = gtk_tree_model_get_path (tree_model: model, iter: &iter);
2374 path_str = gtk_tree_path_to_string (path);
2375
2376 g_signal_handlers_block_by_func (entry,
2377 gtk_combo_box_entry_contents_changed,
2378 combo_box);
2379
2380
2381 g_signal_emit (instance: combo_box, signal_id: combo_box_signals[FORMAT_ENTRY_TEXT], detail: 0,
2382 path_str, &text);
2383
2384 gtk_editable_set_text (GTK_EDITABLE (entry), text);
2385
2386 g_signal_handlers_unblock_by_func (entry,
2387 gtk_combo_box_entry_contents_changed,
2388 combo_box);
2389
2390 gtk_tree_path_free (path);
2391 g_free (mem: text);
2392 g_free (mem: path_str);
2393 }
2394 }
2395}
2396
2397static char *
2398gtk_combo_box_format_entry_text (GtkComboBox *combo_box,
2399 const char *path)
2400{
2401 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2402 GtkTreeModel *model;
2403 GtkTreeIter iter;
2404 char *text = NULL;
2405
2406 if (priv->text_column >= 0)
2407 {
2408 model = gtk_combo_box_get_model (combo_box);
2409 gtk_tree_model_get_iter_from_string (tree_model: model, iter: &iter, path_string: path);
2410
2411 gtk_tree_model_get (tree_model: model, iter: &iter,
2412 priv->text_column, &text,
2413 -1);
2414 }
2415
2416 return text;
2417}
2418
2419static void
2420gtk_combo_box_constructed (GObject *object)
2421{
2422 GtkComboBox *combo_box = GTK_COMBO_BOX (object);
2423 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2424
2425 G_OBJECT_CLASS (gtk_combo_box_parent_class)->constructed (object);
2426
2427 gtk_combo_box_create_child (combo_box);
2428
2429 if (priv->has_entry)
2430 {
2431 priv->text_renderer = gtk_cell_renderer_text_new ();
2432 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box),
2433 cell: priv->text_renderer, TRUE);
2434
2435 gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), index_: -1);
2436 }
2437}
2438
2439static void
2440gtk_combo_box_dispose (GObject* object)
2441{
2442 GtkComboBox *combo_box = GTK_COMBO_BOX (object);
2443 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2444
2445 if (priv->popup_idle_id > 0)
2446 {
2447 g_source_remove (tag: priv->popup_idle_id);
2448 priv->popup_idle_id = 0;
2449 }
2450
2451 if (priv->box)
2452 {
2453 /* destroy things (unparent will kill the latest ref from us)
2454 * last unref on button will destroy the arrow
2455 */
2456 gtk_widget_unparent (widget: priv->box);
2457 priv->box = NULL;
2458 priv->button = NULL;
2459 priv->arrow = NULL;
2460 priv->child = NULL;
2461 priv->cell_view = NULL;
2462 }
2463
2464 if (priv->row_separator_destroy)
2465 priv->row_separator_destroy (priv->row_separator_data);
2466
2467 priv->row_separator_func = NULL;
2468 priv->row_separator_data = NULL;
2469 priv->row_separator_destroy = NULL;
2470
2471 if (priv->popup_widget)
2472 {
2473 /* Stop menu destruction triggering toggle on a now-invalid button */
2474 g_signal_handlers_disconnect_by_func (priv->popup_widget,
2475 gtk_combo_box_menu_hide,
2476 combo_box);
2477 g_clear_pointer (&priv->popup_widget, gtk_widget_unparent);
2478 }
2479
2480 gtk_combo_box_unset_model (combo_box);
2481
2482 G_OBJECT_CLASS (gtk_combo_box_parent_class)->dispose (object);
2483}
2484
2485static gboolean
2486gtk_cell_editable_key_pressed (GtkEventControllerKey *key,
2487 guint keyval,
2488 guint keycode,
2489 GdkModifierType modifiers,
2490 GtkComboBox *combo_box)
2491{
2492 if (keyval == GDK_KEY_Escape)
2493 {
2494 g_object_set (object: combo_box,
2495 first_property_name: "editing-canceled", TRUE,
2496 NULL);
2497 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box));
2498 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box));
2499
2500 return TRUE;
2501 }
2502 else if (keyval == GDK_KEY_Return ||
2503 keyval == GDK_KEY_ISO_Enter ||
2504 keyval == GDK_KEY_KP_Enter)
2505 {
2506 gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (combo_box));
2507 gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (combo_box));
2508
2509 return TRUE;
2510 }
2511
2512 return FALSE;
2513}
2514
2515static void
2516gtk_combo_box_start_editing (GtkCellEditable *cell_editable,
2517 GdkEvent *event)
2518{
2519 GtkComboBox *combo_box = GTK_COMBO_BOX (cell_editable);
2520 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2521 GtkEventController *controller;
2522
2523 priv->is_cell_renderer = TRUE;
2524
2525 controller = gtk_event_controller_key_new ();
2526 g_signal_connect_object (instance: controller, detailed_signal: "key-pressed",
2527 G_CALLBACK (gtk_cell_editable_key_pressed),
2528 gobject: cell_editable, connect_flags: 0);
2529
2530 if (priv->cell_view)
2531 {
2532 gtk_widget_add_controller (widget: priv->button, controller);
2533 gtk_widget_grab_focus (widget: priv->button);
2534 }
2535 else
2536 {
2537 gtk_widget_add_controller (widget: priv->child, controller);
2538
2539 gtk_widget_grab_focus (widget: priv->child);
2540 gtk_widget_set_can_focus (widget: priv->button, FALSE);
2541 }
2542}
2543
2544/**
2545 * gtk_combo_box_set_popup_fixed_width: (attributes org.gtk.Method.set_property=popup-fixed-width)
2546 * @combo_box: a `GtkComboBox`
2547 * @fixed: whether to use a fixed popup width
2548 *
2549 * Specifies whether the popup’s width should be a fixed width.
2550 *
2551 * If @fixed is %TRUE, the popup's width is set to match the
2552 * allocated width of the combo box.
2553 */
2554void
2555gtk_combo_box_set_popup_fixed_width (GtkComboBox *combo_box,
2556 gboolean fixed)
2557{
2558 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2559
2560 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
2561
2562 if (priv->popup_fixed_width != fixed)
2563 {
2564 priv->popup_fixed_width = fixed;
2565
2566 g_object_notify (G_OBJECT (combo_box), property_name: "popup-fixed-width");
2567 }
2568}
2569
2570/**
2571 * gtk_combo_box_get_popup_fixed_width: (attributes org.gtk.Method.get_property=popup-fixed-width)
2572 * @combo_box: a `GtkComboBox`
2573 *
2574 * Gets whether the popup uses a fixed width.
2575 *
2576 * Returns: %TRUE if the popup uses a fixed width
2577 */
2578gboolean
2579gtk_combo_box_get_popup_fixed_width (GtkComboBox *combo_box)
2580{
2581 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2582
2583 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE);
2584
2585 return priv->popup_fixed_width;
2586}
2587
2588/**
2589 * gtk_combo_box_get_row_separator_func: (skip)
2590 * @combo_box: a `GtkComboBox`
2591 *
2592 * Returns the current row separator function.
2593 *
2594 * Returns: (nullable): the current row separator function.
2595 */
2596GtkTreeViewRowSeparatorFunc
2597gtk_combo_box_get_row_separator_func (GtkComboBox *combo_box)
2598{
2599 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2600
2601 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL);
2602
2603 return priv->row_separator_func;
2604}
2605
2606/**
2607 * gtk_combo_box_set_row_separator_func:
2608 * @combo_box: a `GtkComboBox`
2609 * @func: (nullable): a `GtkTreeViewRowSeparatorFunc`
2610 * @data: (nullable): user data to pass to @func
2611 * @destroy: (nullable): destroy notifier for @data
2612 *
2613 * Sets the row separator function, which is used to determine
2614 * whether a row should be drawn as a separator.
2615 *
2616 * If the row separator function is %NULL, no separators are drawn.
2617 * This is the default value.
2618 */
2619void
2620gtk_combo_box_set_row_separator_func (GtkComboBox *combo_box,
2621 GtkTreeViewRowSeparatorFunc func,
2622 gpointer data,
2623 GDestroyNotify destroy)
2624{
2625 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2626
2627 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
2628
2629 if (priv->row_separator_destroy)
2630 priv->row_separator_destroy (priv->row_separator_data);
2631
2632 priv->row_separator_func = func;
2633 priv->row_separator_data = data;
2634 priv->row_separator_destroy = destroy;
2635
2636 gtk_tree_popover_set_row_separator_func (popover: GTK_TREE_POPOVER (ptr: priv->popup_widget),
2637 func: (GtkTreeViewRowSeparatorFunc)gtk_combo_box_row_separator_func,
2638 data: combo_box, NULL);
2639
2640 gtk_widget_queue_draw (GTK_WIDGET (combo_box));
2641}
2642
2643/**
2644 * gtk_combo_box_set_button_sensitivity: (attributes org.gtk.Method.set_property=button-sensitivity)
2645 * @combo_box: a `GtkComboBox`
2646 * @sensitivity: specify the sensitivity of the dropdown button
2647 *
2648 * Sets whether the dropdown button of the combo box should update
2649 * its sensitivity depending on the model contents.
2650 */
2651void
2652gtk_combo_box_set_button_sensitivity (GtkComboBox *combo_box,
2653 GtkSensitivityType sensitivity)
2654{
2655 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2656
2657 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
2658
2659 if (priv->button_sensitivity != sensitivity)
2660 {
2661 priv->button_sensitivity = sensitivity;
2662 gtk_combo_box_update_sensitivity (combo_box);
2663
2664 g_object_notify (G_OBJECT (combo_box), property_name: "button-sensitivity");
2665 }
2666}
2667
2668/**
2669 * gtk_combo_box_get_button_sensitivity: (attributes org.gtk.Method.get_property=button-sensitivity)
2670 * @combo_box: a `GtkComboBox`
2671 *
2672 * Returns whether the combo box sets the dropdown button
2673 * sensitive or not when there are no items in the model.
2674 *
2675 * Returns: %GTK_SENSITIVITY_ON if the dropdown button
2676 * is sensitive when the model is empty, %GTK_SENSITIVITY_OFF
2677 * if the button is always insensitive or %GTK_SENSITIVITY_AUTO
2678 * if it is only sensitive as long as the model has one item to
2679 * be selected.
2680 */
2681GtkSensitivityType
2682gtk_combo_box_get_button_sensitivity (GtkComboBox *combo_box)
2683{
2684 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2685 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE);
2686
2687 return priv->button_sensitivity;
2688}
2689
2690
2691/**
2692 * gtk_combo_box_get_has_entry: (attributes org.gtk.Method.get_property=has-entry)
2693 * @combo_box: a `GtkComboBox`
2694 *
2695 * Returns whether the combo box has an entry.
2696 *
2697 * Returns: whether there is an entry in @combo_box.
2698 */
2699gboolean
2700gtk_combo_box_get_has_entry (GtkComboBox *combo_box)
2701{
2702 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2703 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE);
2704
2705 return priv->has_entry;
2706}
2707
2708/**
2709 * gtk_combo_box_set_entry_text_column:
2710 * @combo_box: A `GtkComboBox`
2711 * @text_column: A column in @model to get the strings from for
2712 * the internal entry
2713 *
2714 * Sets the model column which @combo_box should use to get strings
2715 * from to be @text_column.
2716 *
2717 * For this column no separate
2718 * [class@Gtk.CellRenderer] is needed.
2719 *
2720 * The column @text_column in the model of @combo_box must be of
2721 * type %G_TYPE_STRING.
2722 *
2723 * This is only relevant if @combo_box has been created with
2724 * [property@Gtk.ComboBox:has-entry] as %TRUE.
2725 */
2726void
2727gtk_combo_box_set_entry_text_column (GtkComboBox *combo_box,
2728 int text_column)
2729{
2730 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2731
2732 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
2733 g_return_if_fail (text_column >= 0);
2734 g_return_if_fail (priv->model == NULL || text_column < gtk_tree_model_get_n_columns (priv->model));
2735
2736 if (priv->text_column != text_column)
2737 {
2738 priv->text_column = text_column;
2739
2740 if (priv->text_renderer != NULL)
2741 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box),
2742 cell: priv->text_renderer,
2743 "text", text_column,
2744 NULL);
2745
2746 g_object_notify (G_OBJECT (combo_box), property_name: "entry-text-column");
2747 }
2748}
2749
2750/**
2751 * gtk_combo_box_get_entry_text_column:
2752 * @combo_box: A `GtkComboBox`
2753 *
2754 * Returns the column which @combo_box is using to get the strings
2755 * from to display in the internal entry.
2756 *
2757 * Returns: A column in the data source model of @combo_box.
2758 */
2759int
2760gtk_combo_box_get_entry_text_column (GtkComboBox *combo_box)
2761{
2762 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2763
2764 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0);
2765
2766 return priv->text_column;
2767}
2768
2769static void
2770gtk_combo_box_buildable_add_child (GtkBuildable *buildable,
2771 GtkBuilder *builder,
2772 GObject *child,
2773 const char *type)
2774{
2775 if (GTK_IS_CELL_RENDERER (child))
2776 _gtk_cell_layout_buildable_add_child (buildable, builder, child, type);
2777 else if (GTK_IS_WIDGET (child))
2778 gtk_combo_box_set_child (GTK_COMBO_BOX (buildable), GTK_WIDGET (child));
2779 else
2780 parent_buildable_iface->add_child (buildable, builder, child, type);
2781}
2782
2783static gboolean
2784gtk_combo_box_buildable_custom_tag_start (GtkBuildable *buildable,
2785 GtkBuilder *builder,
2786 GObject *child,
2787 const char *tagname,
2788 GtkBuildableParser *parser,
2789 gpointer *data)
2790{
2791 if (parent_buildable_iface->custom_tag_start (buildable, builder, child,
2792 tagname, parser, data))
2793 return TRUE;
2794
2795 return _gtk_cell_layout_buildable_custom_tag_start (buildable, builder, child,
2796 tagname, parser, data);
2797}
2798
2799static void
2800gtk_combo_box_buildable_custom_tag_end (GtkBuildable *buildable,
2801 GtkBuilder *builder,
2802 GObject *child,
2803 const char *tagname,
2804 gpointer data)
2805{
2806 if (!_gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data))
2807 parent_buildable_iface->custom_tag_end (buildable, builder, child, tagname, data);
2808}
2809
2810static GObject *
2811gtk_combo_box_buildable_get_internal_child (GtkBuildable *buildable,
2812 GtkBuilder *builder,
2813 const char *childname)
2814{
2815 GtkComboBox *combo_box = GTK_COMBO_BOX (buildable);
2816 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2817
2818 if (priv->has_entry && strcmp (s1: childname, s2: "entry") == 0)
2819 return G_OBJECT (priv->child);
2820
2821 return parent_buildable_iface->get_internal_child (buildable, builder, childname);
2822}
2823
2824/**
2825 * gtk_combo_box_set_id_column: (attributes org.gtk.Method.set_property=id-column)
2826 * @combo_box: A `GtkComboBox`
2827 * @id_column: A column in @model to get string IDs for values from
2828 *
2829 * Sets the model column which @combo_box should use to get string IDs
2830 * for values from.
2831 *
2832 * The column @id_column in the model of @combo_box must be of type
2833 * %G_TYPE_STRING.
2834 */
2835void
2836gtk_combo_box_set_id_column (GtkComboBox *combo_box,
2837 int id_column)
2838{
2839 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2840
2841 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
2842
2843 if (id_column != priv->id_column)
2844 {
2845 g_return_if_fail (id_column >= 0);
2846 g_return_if_fail (priv->model == NULL || id_column < gtk_tree_model_get_n_columns (priv->model));
2847
2848 priv->id_column = id_column;
2849
2850 g_object_notify (G_OBJECT (combo_box), property_name: "id-column");
2851 g_object_notify (G_OBJECT (combo_box), property_name: "active-id");
2852 }
2853}
2854
2855/**
2856 * gtk_combo_box_get_id_column: (attributes org.gtk.Method.get_property=id-column)
2857 * @combo_box: A `GtkComboBox`
2858 *
2859 * Returns the column which @combo_box is using to get string IDs
2860 * for values from.
2861 *
2862 * Returns: A column in the data source model of @combo_box.
2863 */
2864int
2865gtk_combo_box_get_id_column (GtkComboBox *combo_box)
2866{
2867 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2868
2869 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0);
2870
2871 return priv->id_column;
2872}
2873
2874/**
2875 * gtk_combo_box_get_active_id: (attributes org.gtk.Method.get_property=active-id)
2876 * @combo_box: a `GtkComboBox`
2877 *
2878 * Returns the ID of the active row of @combo_box.
2879 *
2880 * This value is taken from the active row and the column specified
2881 * by the [property@Gtk.ComboBox:id-column] property of @combo_box
2882 * (see [method@Gtk.ComboBox.set_id_column]).
2883 *
2884 * The returned value is an interned string which means that you can
2885 * compare the pointer by value to other interned strings and that you
2886 * must not free it.
2887 *
2888 * If the [property@Gtk.ComboBox:id-column] property of @combo_box is
2889 * not set, or if no row is active, or if the active row has a %NULL
2890 * ID value, then %NULL is returned.
2891 *
2892 * Returns: (nullable): the ID of the active row
2893 */
2894const char *
2895gtk_combo_box_get_active_id (GtkComboBox *combo_box)
2896{
2897 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2898 GtkTreeModel *model;
2899 GtkTreeIter iter;
2900 int column;
2901
2902 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL);
2903
2904 column = priv->id_column;
2905
2906 if (column < 0)
2907 return NULL;
2908
2909 model = gtk_combo_box_get_model (combo_box);
2910 g_return_val_if_fail (gtk_tree_model_get_column_type (model, column) ==
2911 G_TYPE_STRING, NULL);
2912
2913 if (gtk_combo_box_get_active_iter (combo_box, iter: &iter))
2914 {
2915 const char *interned;
2916 char *id;
2917
2918 gtk_tree_model_get (tree_model: model, iter: &iter, column, &id, -1);
2919 interned = g_intern_string (string: id);
2920 g_free (mem: id);
2921
2922 return interned;
2923 }
2924
2925 return NULL;
2926}
2927
2928/**
2929 * gtk_combo_box_set_active_id: (attributes org.gtk.Method.set_property=active-id)
2930 * @combo_box: a `GtkComboBox`
2931 * @active_id: (nullable): the ID of the row to select
2932 *
2933 * Changes the active row of @combo_box to the one that has an ID equal to
2934 * @active_id.
2935 *
2936 * If @active_id is %NULL, the active row is unset. Rows having
2937 * a %NULL ID string cannot be made active by this function.
2938 *
2939 * If the [property@Gtk.ComboBox:id-column] property of @combo_box is
2940 * unset or if no row has the given ID then the function does nothing
2941 * and returns %FALSE.
2942 *
2943 * Returns: %TRUE if a row with a matching ID was found. If a %NULL
2944 * @active_id was given to unset the active row, the function
2945 * always returns %TRUE.
2946 */
2947gboolean
2948gtk_combo_box_set_active_id (GtkComboBox *combo_box,
2949 const char *active_id)
2950{
2951 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2952 GtkTreeModel *model;
2953 GtkTreeIter iter;
2954 gboolean match = FALSE;
2955 int column;
2956
2957 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), FALSE);
2958
2959 if (active_id == NULL)
2960 {
2961 gtk_combo_box_set_active (combo_box, index_: -1);
2962 return TRUE; /* active row was successfully unset */
2963 }
2964
2965 column = priv->id_column;
2966
2967 if (column < 0)
2968 return FALSE;
2969
2970 model = gtk_combo_box_get_model (combo_box);
2971 g_return_val_if_fail (gtk_tree_model_get_column_type (model, column) ==
2972 G_TYPE_STRING, FALSE);
2973
2974 if (gtk_tree_model_get_iter_first (tree_model: model, iter: &iter))
2975 do {
2976 char *id;
2977
2978 gtk_tree_model_get (tree_model: model, iter: &iter, column, &id, -1);
2979 if (id != NULL)
2980 match = strcmp (s1: id, s2: active_id) == 0;
2981 g_free (mem: id);
2982
2983 if (match)
2984 {
2985 gtk_combo_box_set_active_iter (combo_box, iter: &iter);
2986 break;
2987 }
2988 } while (gtk_tree_model_iter_next (tree_model: model, iter: &iter));
2989
2990 g_object_notify (G_OBJECT (combo_box), property_name: "active-id");
2991
2992 return match;
2993}
2994
2995GtkWidget *
2996gtk_combo_box_get_popup (GtkComboBox *combo_box)
2997{
2998 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
2999
3000 return priv->popup_widget;
3001}
3002
3003/**
3004 * gtk_combo_box_set_child: (attributes org.gtk.Method.set_property=child)
3005 * @combo_box: a `GtkComboBox`
3006 * @child: (nullable): the child widget
3007 *
3008 * Sets the child widget of @combo_box.
3009 */
3010void
3011gtk_combo_box_set_child (GtkComboBox *combo_box,
3012 GtkWidget *child)
3013{
3014 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
3015
3016 g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
3017 g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
3018
3019 if (priv->child)
3020 gtk_combo_box_remove (combo_box, widget: priv->child);
3021
3022 if (child)
3023 gtk_combo_box_add (combo_box, widget: child);
3024
3025 g_object_notify (G_OBJECT (combo_box), property_name: "child");
3026}
3027
3028/**
3029 * gtk_combo_box_get_child: (attributes org.gtk.Method.get_property=child)
3030 * @combo_box: a `GtkComboBox`
3031 *
3032 * Gets the child widget of @combo_box.
3033 *
3034 * Returns: (nullable) (transfer none): the child widget of @combo_box
3035 */
3036GtkWidget *
3037gtk_combo_box_get_child (GtkComboBox *combo_box)
3038{
3039 GtkComboBoxPrivate *priv = gtk_combo_box_get_instance_private (self: combo_box);
3040
3041 g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), NULL);
3042
3043 return priv->child;
3044}
3045
3046

source code of gtk/gtk/gtkcombobox.c