1/*
2 * Copyright © 2019 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 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 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Matthias Clasen <mclasen@redhat.com>
18 */
19
20#include "config.h"
21
22#include "gtkdropdown.h"
23
24#include "gtkbuiltiniconprivate.h"
25#include "gtkintl.h"
26#include "gtklistview.h"
27#include "gtklistitemfactory.h"
28#include "gtksignallistitemfactory.h"
29#include "gtklistitemwidgetprivate.h"
30#include "gtkpopover.h"
31#include "gtkprivate.h"
32#include "gtksingleselection.h"
33#include "gtkfilterlistmodel.h"
34#include "gtkstringfilter.h"
35#include "gtkmultifilter.h"
36#include "gtkwidgetprivate.h"
37#include "gtknative.h"
38#include "gtktogglebutton.h"
39#include "gtkexpression.h"
40#include "gtkstack.h"
41#include "gtksearchentry.h"
42#include "gtklabel.h"
43#include "gtklistitem.h"
44#include "gtkbuildable.h"
45#include "gtkbuilderprivate.h"
46#include "gtkstringlist.h"
47#include "gtkbox.h"
48
49/**
50 * GtkDropDown:
51 *
52 * `GtkDropDown` is a widget that allows the user to choose an item
53 * from a list of options.
54 *
55 * ![An example GtkDropDown](drop-down.png)
56 *
57 * The `GtkDropDown` displays the selected choice.
58 *
59 * The options are given to `GtkDropDown` in the form of `GListModel`
60 * and how the individual options are represented is determined by
61 * a [class@Gtk.ListItemFactory]. The default factory displays simple strings.
62 *
63 * `GtkDropDown` knows how to obtain strings from the items in a
64 * [class@Gtk.StringList]; for other models, you have to provide an expression
65 * to find the strings via [method@Gtk.DropDown.set_expression].
66 *
67 * `GtkDropDown` can optionally allow search in the popup, which is
68 * useful if the list of options is long. To enable the search entry,
69 * use [method@Gtk.DropDown.set_enable_search].
70 *
71 * # CSS nodes
72 *
73 * `GtkDropDown` has a single CSS node with name dropdown,
74 * with the button and popover nodes as children.
75 *
76 * # Accessibility
77 *
78 * `GtkDropDown` uses the %GTK_ACCESSIBLE_ROLE_COMBO_BOX role.
79 */
80
81struct _GtkDropDown
82{
83 GtkWidget parent_instance;
84
85 GtkListItemFactory *factory;
86 GtkListItemFactory *list_factory;
87 GListModel *model;
88 GtkSelectionModel *selection;
89 GListModel *filter_model;
90 GtkSelectionModel *popup_selection;
91
92 GtkWidget *popup;
93 GtkWidget *button;
94 GtkWidget *arrow;
95
96 GtkWidget *popup_list;
97 GtkWidget *button_stack;
98 GtkWidget *button_item;
99 GtkWidget *button_placeholder;
100 GtkWidget *search_box;
101 GtkWidget *search_entry;
102
103 GtkExpression *expression;
104
105 guint enable_search : 1;
106 guint show_arrow : 1;
107};
108
109struct _GtkDropDownClass
110{
111 GtkWidgetClass parent_class;
112};
113
114enum
115{
116 PROP_0,
117 PROP_FACTORY,
118 PROP_LIST_FACTORY,
119 PROP_MODEL,
120 PROP_SELECTED,
121 PROP_SELECTED_ITEM,
122 PROP_ENABLE_SEARCH,
123 PROP_EXPRESSION,
124 PROP_SHOW_ARROW,
125
126 N_PROPS
127};
128
129enum
130{
131 ACTIVATE,
132 LAST_SIGNAL
133};
134
135G_DEFINE_TYPE (GtkDropDown, gtk_drop_down, GTK_TYPE_WIDGET)
136
137static GParamSpec *properties[N_PROPS] = { NULL, };
138static guint signals[LAST_SIGNAL] = { 0 };
139
140static void
141button_toggled (GtkWidget *widget,
142 gpointer data)
143{
144 GtkDropDown *self = data;
145
146 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
147 gtk_popover_popup (GTK_POPOVER (self->popup));
148 else
149 gtk_popover_popdown (GTK_POPOVER (self->popup));
150}
151
152static void
153popover_closed (GtkPopover *popover,
154 gpointer data)
155{
156 GtkDropDown *self = data;
157
158 gtk_editable_set_text (GTK_EDITABLE (self->search_entry), text: "");
159 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE);
160}
161
162static void
163row_activated (GtkListView *listview,
164 guint position,
165 gpointer data)
166{
167 GtkDropDown *self = data;
168 GtkFilter *filter;
169
170 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE);
171 gtk_popover_popdown (GTK_POPOVER (self->popup));
172
173 /* reset the filter so positions are 1-1 */
174 filter = gtk_filter_list_model_get_filter (self: GTK_FILTER_LIST_MODEL (ptr: self->filter_model));
175 if (GTK_IS_STRING_FILTER (ptr: filter))
176 gtk_string_filter_set_search (self: GTK_STRING_FILTER (ptr: filter), search: "");
177 gtk_drop_down_set_selected (self, position: gtk_single_selection_get_selected (self: GTK_SINGLE_SELECTION (ptr: self->popup_selection)));
178}
179
180static void
181selection_changed (GtkSingleSelection *selection,
182 GParamSpec *pspec,
183 gpointer data)
184{
185 GtkDropDown *self = data;
186 guint selected;
187 gpointer item;
188 GtkFilter *filter;
189
190 selected = gtk_single_selection_get_selected (self: GTK_SINGLE_SELECTION (ptr: self->selection));
191 item = gtk_single_selection_get_selected_item (self: GTK_SINGLE_SELECTION (ptr: self->selection));
192
193 if (selected == GTK_INVALID_LIST_POSITION)
194 {
195 gtk_stack_set_visible_child_name (GTK_STACK (self->button_stack), name: "empty");
196 }
197 else
198 {
199 gtk_stack_set_visible_child_name (GTK_STACK (self->button_stack), name: "item");
200 gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (self->button_item), position: selected, item, FALSE);
201 }
202
203 /* reset the filter so positions are 1-1 */
204 filter = gtk_filter_list_model_get_filter (self: GTK_FILTER_LIST_MODEL (ptr: self->filter_model));
205 if (GTK_IS_STRING_FILTER (ptr: filter))
206 gtk_string_filter_set_search (self: GTK_STRING_FILTER (ptr: filter), search: "");
207 gtk_single_selection_set_selected (self: GTK_SINGLE_SELECTION (ptr: self->popup_selection), position: selected);
208
209 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED]);
210 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SELECTED_ITEM]);
211}
212
213static void
214gtk_drop_down_activate (GtkDropDown *self)
215{
216 gtk_widget_activate (widget: self->button);
217}
218
219static void
220update_filter (GtkDropDown *self)
221{
222 if (self->filter_model)
223 {
224 GtkFilter *filter;
225
226 if (self->expression)
227 {
228 filter = GTK_FILTER (ptr: gtk_string_filter_new (expression: gtk_expression_ref (self: self->expression)));
229 gtk_string_filter_set_match_mode (self: GTK_STRING_FILTER (ptr: filter), mode: GTK_STRING_FILTER_MATCH_MODE_PREFIX);
230 }
231 else
232 filter = GTK_FILTER (ptr: gtk_every_filter_new ());
233 gtk_filter_list_model_set_filter (self: GTK_FILTER_LIST_MODEL (ptr: self->filter_model), filter);
234 g_object_unref (object: filter);
235 }
236}
237
238static void
239search_changed (GtkSearchEntry *entry, gpointer data)
240{
241 GtkDropDown *self = data;
242 const char *text;
243 GtkFilter *filter;
244
245 text = gtk_editable_get_text (GTK_EDITABLE (entry));
246
247 filter = gtk_filter_list_model_get_filter (self: GTK_FILTER_LIST_MODEL (ptr: self->filter_model));
248 if (GTK_IS_STRING_FILTER (ptr: filter))
249 gtk_string_filter_set_search (self: GTK_STRING_FILTER (ptr: filter), search: text);
250}
251
252static void
253search_stop (GtkSearchEntry *entry, gpointer data)
254{
255 GtkDropDown *self = data;
256 GtkFilter *filter;
257
258 filter = gtk_filter_list_model_get_filter (self: GTK_FILTER_LIST_MODEL (ptr: self->filter_model));
259 if (GTK_IS_STRING_FILTER (ptr: filter))
260 {
261 if (gtk_string_filter_get_search (self: GTK_STRING_FILTER (ptr: filter)))
262 gtk_string_filter_set_search (self: GTK_STRING_FILTER (ptr: filter), NULL);
263 else
264 gtk_popover_popdown (GTK_POPOVER (self->popup));
265 }
266}
267
268static void
269gtk_drop_down_dispose (GObject *object)
270{
271 GtkDropDown *self = GTK_DROP_DOWN (ptr: object);
272
273 g_clear_pointer (&self->popup, gtk_widget_unparent);
274 g_clear_pointer (&self->button, gtk_widget_unparent);
275
276 g_clear_object (&self->model);
277 if (self->selection)
278 g_signal_handlers_disconnect_by_func (self->selection, selection_changed, self);
279 g_clear_object (&self->filter_model);
280 g_clear_pointer (&self->expression, gtk_expression_unref);
281 g_clear_object (&self->selection);
282 g_clear_object (&self->popup_selection);
283 g_clear_object (&self->factory);
284 g_clear_object (&self->list_factory);
285
286 G_OBJECT_CLASS (gtk_drop_down_parent_class)->dispose (object);
287}
288
289static void
290gtk_drop_down_get_property (GObject *object,
291 guint property_id,
292 GValue *value,
293 GParamSpec *pspec)
294{
295 GtkDropDown *self = GTK_DROP_DOWN (ptr: object);
296
297 switch (property_id)
298 {
299 case PROP_FACTORY:
300 g_value_set_object (value, v_object: self->factory);
301 break;
302
303 case PROP_LIST_FACTORY:
304 g_value_set_object (value, v_object: self->list_factory);
305 break;
306
307 case PROP_MODEL:
308 g_value_set_object (value, v_object: self->model);
309 break;
310
311 case PROP_SELECTED:
312 g_value_set_uint (value, v_uint: gtk_drop_down_get_selected (self));
313 break;
314
315 case PROP_SELECTED_ITEM:
316 g_value_set_object (value, v_object: gtk_drop_down_get_selected_item (self));
317 break;
318
319 case PROP_ENABLE_SEARCH:
320 g_value_set_boolean (value, v_boolean: self->enable_search);
321 break;
322
323 case PROP_EXPRESSION:
324 gtk_value_set_expression (value, expression: self->expression);
325 break;
326
327 case PROP_SHOW_ARROW:
328 g_value_set_boolean (value, v_boolean: gtk_drop_down_get_show_arrow (self));
329 break;
330
331 default:
332 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
333 break;
334 }
335}
336
337static void
338gtk_drop_down_set_property (GObject *object,
339 guint property_id,
340 const GValue *value,
341 GParamSpec *pspec)
342{
343 GtkDropDown *self = GTK_DROP_DOWN (ptr: object);
344
345 switch (property_id)
346 {
347 case PROP_FACTORY:
348 gtk_drop_down_set_factory (self, factory: g_value_get_object (value));
349 break;
350
351 case PROP_LIST_FACTORY:
352 gtk_drop_down_set_list_factory (self, factory: g_value_get_object (value));
353 break;
354
355 case PROP_MODEL:
356 gtk_drop_down_set_model (self, model: g_value_get_object (value));
357 break;
358
359 case PROP_SELECTED:
360 gtk_drop_down_set_selected (self, position: g_value_get_uint (value));
361 break;
362
363 case PROP_ENABLE_SEARCH:
364 gtk_drop_down_set_enable_search (self, enable_search: g_value_get_boolean (value));
365 break;
366
367 case PROP_EXPRESSION:
368 gtk_drop_down_set_expression (self, expression: gtk_value_get_expression (value));
369 break;
370
371 case PROP_SHOW_ARROW:
372 gtk_drop_down_set_show_arrow (self, show_arrow: g_value_get_boolean (value));
373 break;
374
375 default:
376 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
377 break;
378 }
379}
380
381static void
382gtk_drop_down_measure (GtkWidget *widget,
383 GtkOrientation orientation,
384 int size,
385 int *minimum,
386 int *natural,
387 int *minimum_baseline,
388 int *natural_baseline)
389{
390 GtkDropDown *self = GTK_DROP_DOWN (ptr: widget);
391
392 gtk_widget_measure (widget: self->button,
393 orientation,
394 for_size: size,
395 minimum, natural,
396 minimum_baseline, natural_baseline);
397}
398
399static void
400gtk_drop_down_size_allocate (GtkWidget *widget,
401 int width,
402 int height,
403 int baseline)
404{
405 GtkDropDown *self = GTK_DROP_DOWN (ptr: widget);
406
407 gtk_widget_size_allocate (widget: self->button, allocation: &(GtkAllocation) { 0, 0, width, height }, baseline);
408
409 gtk_widget_set_size_request (widget: self->popup, width, height: -1);
410 gtk_widget_queue_resize (widget: self->popup);
411
412 gtk_popover_present (GTK_POPOVER (self->popup));
413}
414
415static gboolean
416gtk_drop_down_focus (GtkWidget *widget,
417 GtkDirectionType direction)
418{
419 GtkDropDown *self = GTK_DROP_DOWN (ptr: widget);
420
421 if (self->popup && gtk_widget_get_visible (widget: self->popup))
422 return gtk_widget_child_focus (widget: self->popup, direction);
423 else
424 return gtk_widget_child_focus (widget: self->button, direction);
425}
426
427static gboolean
428gtk_drop_down_grab_focus (GtkWidget *widget)
429{
430 GtkDropDown *self = GTK_DROP_DOWN (ptr: widget);
431
432 return gtk_widget_grab_focus (widget: self->button);
433}
434
435
436static void
437gtk_drop_down_class_init (GtkDropDownClass *klass)
438{
439 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
440 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
441
442 gobject_class->dispose = gtk_drop_down_dispose;
443 gobject_class->get_property = gtk_drop_down_get_property;
444 gobject_class->set_property = gtk_drop_down_set_property;
445
446 widget_class->measure = gtk_drop_down_measure;
447 widget_class->size_allocate = gtk_drop_down_size_allocate;
448 widget_class->focus = gtk_drop_down_focus;
449 widget_class->grab_focus = gtk_drop_down_grab_focus;
450
451 /**
452 * GtkDropDown:factory: (attributes org.gtk.Property.get=gtk_drop_down_get_factory org.gtk.Property.set=gtk_drop_down_set_factory)
453 *
454 * Factory for populating list items.
455 */
456 properties[PROP_FACTORY] =
457 g_param_spec_object (name: "factory",
458 P_("Factory"),
459 P_("Factory for populating list items"),
460 GTK_TYPE_LIST_ITEM_FACTORY,
461 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
462
463 /**
464 * GtkDropDown:list-factory: (attributes org.gtk.Property.get=gtk_drop_down_get_list_factory org.gtk.Property.set=gtk_drop_down_set_list_factory)
465 *
466 * The factory for populating list items in the popup.
467 *
468 * If this is not set, [property@Gtk.DropDown:factory] is used.
469 */
470 properties[PROP_LIST_FACTORY] =
471 g_param_spec_object (name: "list-factory",
472 P_("List Factory"),
473 P_("Factory for populating list items"),
474 GTK_TYPE_LIST_ITEM_FACTORY,
475 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
476
477 /**
478 * GtkDropDown:model: (attributes org.gtk.Property.get=gtk_drop_down_get_model org.gtk.Property.set=gtk_drop_down_set_model)
479 *
480 * Model for the displayed items.
481 */
482 properties[PROP_MODEL] =
483 g_param_spec_object (name: "model",
484 P_("Model"),
485 P_("Model for the displayed items"),
486 G_TYPE_LIST_MODEL,
487 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
488
489 /**
490 * GtkDropDown:selected: (attributes org.gtk.Property.get=gtk_drop_down_get_selected org.gtk.Property.set=gtk_drop_down_set_selected)
491 *
492 * The position of the selected item.
493 *
494 * If no item is selected, the property has the value
495 * %GTK_INVALID_LIST_POSITION.
496 */
497 properties[PROP_SELECTED] =
498 g_param_spec_uint (name: "selected",
499 P_("Selected"),
500 P_("Position of the selected item"),
501 minimum: 0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
502 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
503
504 /**
505 * GtkDropDown:selected-item: (attributes org.gtk.Property.get=gtk_drop_down_get_selected_item)
506 *
507 * The selected item.
508 */
509 properties[PROP_SELECTED_ITEM] =
510 g_param_spec_object (name: "selected-item",
511 P_("Selected Item"),
512 P_("The selected item"),
513 G_TYPE_OBJECT,
514 flags: G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
515
516 /**
517 * GtkDropDown:enable-search: (attributes org.gtk.Property.get=gtk_drop_down_get_enable_search org.gtk.Property.set=gtk_drop_down_set_enable_search)
518 *
519 * Whether to show a search entry in the popup.
520 *
521 * Note that search requires [property@Gtk.DropDown:expression]
522 * to be set.
523 */
524 properties[PROP_ENABLE_SEARCH] =
525 g_param_spec_boolean (name: "enable-search",
526 P_("Enable search"),
527 P_("Whether to show a search entry in the popup"),
528 FALSE,
529 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
530
531 /**
532 * GtkDropDown:expression: (type GtkExpression) (attributes org.gtk.Property.get=gtk_drop_down_get_expression org.gtk.Property.set=gtk_drop_down_set_expression)
533 *
534 * An expression to evaluate to obtain strings to match against the search
535 * term.
536 *
537 * See [property@Gtk.DropDown:enable-search] for how to enable search.
538 * If [property@Gtk.DropDown:factory] is not set, the expression is also
539 * used to bind strings to labels produced by a default factory.
540 */
541 properties[PROP_EXPRESSION] =
542 gtk_param_spec_expression (name: "expression",
543 P_("Expression"),
544 P_("Expression to determine strings to search for"),
545 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
546
547 /**
548 * GtkDropDown:show-arrow: (attributes org.gtk.Property.get=gtk_drop_down_get_show_arrow org.gtk.Property.set=gtk_drop_down_set_show_arrow)
549 *
550 * Whether to show an arrow within the GtkDropDown widget.
551 *
552 * Since: 4.6
553 */
554 properties[PROP_SHOW_ARROW] =
555 g_param_spec_boolean (name: "show-arrow",
556 P_("Show arrow"),
557 P_("Whether to show an arrow within the widget"),
558 TRUE,
559 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
560
561 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
562
563 /**
564 * GtkDropDown::activate:
565 * @widget: the object which received the signal.
566 *
567 * Emitted to when the drop down is activated.
568 *
569 * The `::activate` signal on `GtkDropDown` is an action signal and
570 * emitting it causes the drop down to pop up its dropdown.
571 *
572 * Since: 4.6
573 */
574 signals[ACTIVATE] =
575 g_signal_new_class_handler (I_ ("activate"),
576 G_OBJECT_CLASS_TYPE (gobject_class),
577 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
578 G_CALLBACK (gtk_drop_down_activate),
579 NULL, NULL,
580 NULL,
581 G_TYPE_NONE, n_params: 0);
582
583 gtk_widget_class_set_activate_signal (widget_class, signal_id: signals[ACTIVATE]);
584
585 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/ui/gtkdropdown.ui");
586 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, arrow);
587 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, button);
588 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, button_stack);
589 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, button_item);
590 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, popup);
591 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, popup_list);
592 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, search_box);
593 gtk_widget_class_bind_template_child (widget_class, GtkDropDown, search_entry);
594
595 gtk_widget_class_bind_template_callback (widget_class, row_activated);
596 gtk_widget_class_bind_template_callback (widget_class, button_toggled);
597 gtk_widget_class_bind_template_callback (widget_class, popover_closed);
598 gtk_widget_class_bind_template_callback (widget_class, search_changed);
599 gtk_widget_class_bind_template_callback (widget_class, search_stop);
600
601 gtk_widget_class_set_css_name (widget_class, I_("dropdown"));
602 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_COMBO_BOX);
603}
604
605static void
606setup_item (GtkSignalListItemFactory *factory,
607 GtkListItem *list_item,
608 gpointer data)
609{
610 GtkWidget *box;
611 GtkWidget *label;
612 GtkWidget *icon;
613
614 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
615 label = gtk_label_new (NULL);
616 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
617 gtk_box_append (GTK_BOX (box), child: label);
618 icon = gtk_image_new_from_icon_name (icon_name: "object-select-symbolic");
619 gtk_box_append (GTK_BOX (box), child: icon);
620 gtk_list_item_set_child (self: list_item, child: box);
621}
622
623static void
624selected_item_changed (GtkDropDown *self,
625 GParamSpec *pspec,
626 GtkListItem *list_item)
627{
628 GtkWidget *box;
629 GtkWidget *icon;
630
631 box = gtk_list_item_get_child (self: list_item);
632 icon = gtk_widget_get_last_child (widget: box);
633
634 if (gtk_drop_down_get_selected_item (self) == gtk_list_item_get_item (self: list_item))
635 gtk_widget_set_opacity (widget: icon, opacity: 1.0);
636 else
637 gtk_widget_set_opacity (widget: icon, opacity: 0.0);
638}
639
640static void
641bind_item (GtkSignalListItemFactory *factory,
642 GtkListItem *list_item,
643 gpointer data)
644{
645 GtkDropDown *self = data;
646 gpointer item;
647 GtkWidget *box;
648 GtkWidget *label;
649 GtkWidget *icon;
650 GValue value = G_VALUE_INIT;
651
652 item = gtk_list_item_get_item (self: list_item);
653 box = gtk_list_item_get_child (self: list_item);
654 label = gtk_widget_get_first_child (widget: box);
655 icon = gtk_widget_get_last_child (widget: box);
656
657 if (self->expression &&
658 gtk_expression_evaluate (self: self->expression, this_: item, value: &value))
659 {
660 gtk_label_set_label (GTK_LABEL (label), str: g_value_get_string (value: &value));
661 g_value_unset (value: &value);
662 }
663 else if (GTK_IS_STRING_OBJECT (ptr: item))
664 {
665 const char *string;
666
667 string = gtk_string_object_get_string (self: GTK_STRING_OBJECT (ptr: item));
668 gtk_label_set_label (GTK_LABEL (label), str: string);
669 }
670 else
671 {
672 g_critical ("Either GtkDropDown:factory or GtkDropDown:expression must be set");
673 }
674
675 if (gtk_widget_get_ancestor (widget: box, GTK_TYPE_POPOVER) == self->popup)
676 {
677 gtk_widget_show (widget: icon);
678 g_signal_connect (self, "notify::selected-item",
679 G_CALLBACK (selected_item_changed), list_item);
680 selected_item_changed (self, NULL, list_item);
681 }
682 else
683 {
684 gtk_widget_hide (widget: icon);
685 }
686}
687
688static void
689unbind_item (GtkSignalListItemFactory *factory,
690 GtkListItem *list_item,
691 gpointer data)
692{
693 GtkDropDown *self = data;
694
695 g_signal_handlers_disconnect_by_func (self, selected_item_changed, list_item);
696}
697
698static void
699set_default_factory (GtkDropDown *self)
700{
701 GtkListItemFactory *factory;
702
703 factory = gtk_signal_list_item_factory_new ();
704
705 g_signal_connect (factory, "setup", G_CALLBACK (setup_item), self);
706 g_signal_connect (factory, "bind", G_CALLBACK (bind_item), self);
707 g_signal_connect (factory, "unbind", G_CALLBACK (unbind_item), self);
708
709 gtk_drop_down_set_factory (self, factory);
710
711 g_object_unref (object: factory);
712}
713
714static void
715gtk_drop_down_init (GtkDropDown *self)
716{
717 g_type_ensure (GTK_TYPE_BUILTIN_ICON);
718 g_type_ensure (GTK_TYPE_LIST_ITEM_WIDGET);
719
720 gtk_widget_init_template (GTK_WIDGET (self));
721
722 self->show_arrow = gtk_widget_get_visible (widget: self->arrow);
723
724 set_default_factory (self);
725}
726
727/**
728 * gtk_drop_down_new:
729 * @model: (transfer full) (nullable): the model to use
730 * @expression: (transfer full) (nullable): the expression to use
731 *
732 * Creates a new `GtkDropDown`.
733 *
734 * You may want to call [method@Gtk.DropDown.set_factory]
735 * to set up a way to map its items to widgets.
736 *
737 * Returns: a new `GtkDropDown`
738 */
739GtkWidget *
740gtk_drop_down_new (GListModel *model,
741 GtkExpression *expression)
742{
743 GtkWidget *self;
744
745 self = g_object_new (GTK_TYPE_DROP_DOWN,
746 first_property_name: "model", model,
747 "expression", expression,
748 NULL);
749
750 /* we're consuming the references */
751 g_clear_object (&model);
752 g_clear_pointer (&expression, gtk_expression_unref);
753
754 return self;
755}
756
757/**
758 * gtk_drop_down_new_from_strings:
759 * @strings: (array zero-terminated=1): The strings to put in the dropdown
760 *
761 * Creates a new `GtkDropDown` that is populated with
762 * the strings.
763 *
764 * Returns: a new `GtkDropDown`
765 */
766GtkWidget *
767gtk_drop_down_new_from_strings (const char * const *strings)
768{
769 return gtk_drop_down_new (model: G_LIST_MODEL (ptr: gtk_string_list_new (strings)), NULL);
770}
771
772/**
773 * gtk_drop_down_get_model: (attributes org.gtk.Method.get_property=model)
774 * @self: a `GtkDropDown`
775 *
776 * Gets the model that provides the displayed items.
777 *
778 * Returns: (nullable) (transfer none): The model in use
779 */
780GListModel *
781gtk_drop_down_get_model (GtkDropDown *self)
782{
783 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL);
784
785 return self->model;
786}
787
788/**
789 * gtk_drop_down_set_model: (attributes org.gtk.Method.set_property=model)
790 * @self: a `GtkDropDown`
791 * @model: (nullable) (transfer none): the model to use
792 *
793 * Sets the `GListModel` to use.
794 */
795void
796gtk_drop_down_set_model (GtkDropDown *self,
797 GListModel *model)
798{
799 g_return_if_fail (GTK_IS_DROP_DOWN (self));
800 g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
801
802 if (!g_set_object (&self->model, model))
803 return;
804
805 if (model == NULL)
806 {
807 gtk_list_view_set_model (GTK_LIST_VIEW (self->popup_list), NULL);
808
809 if (self->selection)
810 g_signal_handlers_disconnect_by_func (self->selection, selection_changed, self);
811
812 g_clear_object (&self->selection);
813 g_clear_object (&self->filter_model);
814 g_clear_object (&self->popup_selection);
815 }
816 else
817 {
818 GListModel *filter_model;
819 GtkSelectionModel *selection;
820
821 filter_model = G_LIST_MODEL (ptr: gtk_filter_list_model_new (g_object_ref (model), NULL));
822 g_set_object (&self->filter_model, filter_model);
823
824 update_filter (self);
825
826 selection = GTK_SELECTION_MODEL (ptr: gtk_single_selection_new (model: filter_model));
827 g_set_object (&self->popup_selection, selection);
828 gtk_list_view_set_model (GTK_LIST_VIEW (self->popup_list), model: selection);
829 g_object_unref (object: selection);
830
831 selection = GTK_SELECTION_MODEL (ptr: gtk_single_selection_new (g_object_ref (model)));
832 g_set_object (&self->selection, selection);
833 g_object_unref (object: selection);
834
835 g_signal_connect (self->selection, "notify::selected", G_CALLBACK (selection_changed), self);
836 selection_changed (selection: GTK_SINGLE_SELECTION (ptr: self->selection), NULL, data: self);
837 }
838
839 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODEL]);
840}
841
842/**
843 * gtk_drop_down_get_factory: (attributes org.gtk.Method.get_property=factory)
844 * @self: a `GtkDropDown`
845 *
846 * Gets the factory that's currently used to populate list items.
847 *
848 * The factory returned by this function is always used for the
849 * item in the button. It is also used for items in the popup
850 * if [property@Gtk.DropDown:list-factory] is not set.
851 *
852 * Returns: (nullable) (transfer none): The factory in use
853 */
854GtkListItemFactory *
855gtk_drop_down_get_factory (GtkDropDown *self)
856{
857 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL);
858
859 return self->factory;
860}
861
862/**
863 * gtk_drop_down_set_factory: (attributes org.gtk.Method.set_property=factory)
864 * @self: a `GtkDropDown`
865 * @factory: (nullable) (transfer none): the factory to use
866 *
867 * Sets the `GtkListItemFactory` to use for populating list items.
868 */
869void
870gtk_drop_down_set_factory (GtkDropDown *self,
871 GtkListItemFactory *factory)
872{
873 g_return_if_fail (GTK_IS_DROP_DOWN (self));
874 g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));
875
876 if (!g_set_object (&self->factory, factory))
877 return;
878
879 gtk_list_item_widget_set_factory (GTK_LIST_ITEM_WIDGET (self->button_item), factory);
880 if (self->list_factory == NULL)
881 gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), factory);
882
883 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FACTORY]);
884}
885
886/**
887 * gtk_drop_down_get_list_factory: (attributes org.gtk.Method.get_property=list-factory)
888 * @self: a `GtkDropDown`
889 *
890 * Gets the factory that's currently used to populate list items in the popup.
891 *
892 * Returns: (nullable) (transfer none): The factory in use
893 */
894GtkListItemFactory *
895gtk_drop_down_get_list_factory (GtkDropDown *self)
896{
897 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL);
898
899 return self->list_factory;
900}
901
902/**
903 * gtk_drop_down_set_list_factory: (attributes org.gtk.Method.set_property=list-factory)
904 * @self: a `GtkDropDown`
905 * @factory: (nullable) (transfer none): the factory to use
906 *
907 * Sets the `GtkListItemFactory` to use for populating list items in the popup.
908 */
909void
910gtk_drop_down_set_list_factory (GtkDropDown *self,
911 GtkListItemFactory *factory)
912{
913 g_return_if_fail (GTK_IS_DROP_DOWN (self));
914 g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));
915
916 if (!g_set_object (&self->list_factory, factory))
917 return;
918
919 if (self->list_factory != NULL)
920 gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), factory: self->list_factory);
921 else
922 gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), factory: self->factory);
923
924 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LIST_FACTORY]);
925}
926
927/**
928 * gtk_drop_down_set_selected: (attributes org.gtk.Method.set_property=selected)
929 * @self: a `GtkDropDown`
930 * @position: the position of the item to select, or %GTK_INVALID_LIST_POSITION
931 *
932 * Selects the item at the given position.
933 */
934void
935gtk_drop_down_set_selected (GtkDropDown *self,
936 guint position)
937{
938 g_return_if_fail (GTK_IS_DROP_DOWN (self));
939
940 if (self->selection == NULL)
941 return;
942
943 if (gtk_single_selection_get_selected (self: GTK_SINGLE_SELECTION (ptr: self->selection)) == position)
944 return;
945
946 gtk_single_selection_set_selected (self: GTK_SINGLE_SELECTION (ptr: self->selection), position);
947}
948
949/**
950 * gtk_drop_down_get_selected: (attributes org.gtk.Method.get_property=selected)
951 * @self: a `GtkDropDown`
952 *
953 * Gets the position of the selected item.
954 *
955 * Returns: the position of the selected item, or %GTK_INVALID_LIST_POSITION
956 * if not item is selected
957 */
958guint
959gtk_drop_down_get_selected (GtkDropDown *self)
960{
961 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), GTK_INVALID_LIST_POSITION);
962
963 if (self->selection == NULL)
964 return GTK_INVALID_LIST_POSITION;
965
966 return gtk_single_selection_get_selected (self: GTK_SINGLE_SELECTION (ptr: self->selection));
967}
968
969/**
970 * gtk_drop_down_get_selected_item: (attributes org.gtk.Method.get_property=selected-item)
971 * @self: a `GtkDropDown`
972 *
973 * Gets the selected item. If no item is selected, %NULL is returned.
974 *
975 * Returns: (transfer none) (type GObject) (nullable): The selected item
976 */
977gpointer
978gtk_drop_down_get_selected_item (GtkDropDown *self)
979{
980 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL);
981
982 if (self->selection == NULL)
983 return NULL;
984
985 return gtk_single_selection_get_selected_item (self: GTK_SINGLE_SELECTION (ptr: self->selection));
986}
987
988/**
989 * gtk_drop_down_set_enable_search: (attributes org.gtk.Method.set_property=enable-search)
990 * @self: a `GtkDropDown`
991 * @enable_search: whether to enable search
992 *
993 * Sets whether a search entry will be shown in the popup that
994 * allows to search for items in the list.
995 *
996 * Note that [property@Gtk.DropDown:expression] must be set for
997 * search to work.
998 */
999void
1000gtk_drop_down_set_enable_search (GtkDropDown *self,
1001 gboolean enable_search)
1002{
1003 g_return_if_fail (GTK_IS_DROP_DOWN (self));
1004
1005 enable_search = !!enable_search;
1006
1007 if (self->enable_search == enable_search)
1008 return;
1009
1010 self->enable_search = enable_search;
1011
1012 gtk_editable_set_text (GTK_EDITABLE (self->search_entry), text: "");
1013 gtk_widget_set_visible (widget: self->search_box, visible: enable_search);
1014
1015 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ENABLE_SEARCH]);
1016}
1017
1018/**
1019 * gtk_drop_down_get_enable_search: (attributes org.gtk.Method.set_property=enable-search)
1020 * @self: a `GtkDropDown`
1021 *
1022 * Returns whether search is enabled.
1023 *
1024 * Returns: %TRUE if the popup includes a search entry
1025 */
1026gboolean
1027gtk_drop_down_get_enable_search (GtkDropDown *self)
1028{
1029 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), FALSE);
1030
1031 return self->enable_search;
1032}
1033
1034/**
1035 * gtk_drop_down_set_expression: (attributes org.gtk.Method.set_property=expression)
1036 * @self: a `GtkDropDown`
1037 * @expression: (nullable): a `GtkExpression`
1038 *
1039 * Sets the expression that gets evaluated to obtain strings from items.
1040 *
1041 * This is used for search in the popup. The expression must have
1042 * a value type of %G_TYPE_STRING.
1043 */
1044void
1045gtk_drop_down_set_expression (GtkDropDown *self,
1046 GtkExpression *expression)
1047{
1048 g_return_if_fail (GTK_IS_DROP_DOWN (self));
1049 g_return_if_fail (expression == NULL ||
1050 gtk_expression_get_value_type (expression) == G_TYPE_STRING);
1051
1052 if (self->expression == expression)
1053 return;
1054
1055 if (self->expression)
1056 gtk_expression_unref (self: self->expression);
1057 self->expression = expression;
1058 if (self->expression)
1059 gtk_expression_ref (self: self->expression);
1060
1061 update_filter (self);
1062
1063 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_EXPRESSION]);
1064}
1065
1066/**
1067 * gtk_drop_down_get_expression: (attributes org.gtk.Method.get_property=expression)
1068 * @self: a `GtkDropDown`
1069 *
1070 * Gets the expression set that is used to obtain strings from items.
1071 *
1072 * See [method@Gtk.DropDown.set_expression].
1073 *
1074 * Returns: (nullable) (transfer none): a `GtkExpression`
1075 */
1076GtkExpression *
1077gtk_drop_down_get_expression (GtkDropDown *self)
1078{
1079 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL);
1080
1081 return self->expression;
1082}
1083
1084/**
1085 * gtk_drop_down_set_show_arrow: (attributes org.gtk.Method.set_property=show-arrow)
1086 * @self: a `GtkDropDown`
1087 * @show_arrow: whether to show an arrow within the widget
1088 *
1089 * Sets whether an arrow will be displayed within the widget.
1090 *
1091 * Since: 4.6
1092 */
1093void
1094gtk_drop_down_set_show_arrow (GtkDropDown *self,
1095 gboolean show_arrow)
1096{
1097 g_return_if_fail (GTK_IS_DROP_DOWN (self));
1098
1099 show_arrow = !!show_arrow;
1100
1101 if (self->show_arrow == show_arrow)
1102 return;
1103
1104 self->show_arrow = show_arrow;
1105
1106 gtk_widget_set_visible (widget: self->arrow, visible: show_arrow);
1107 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_SHOW_ARROW]);
1108}
1109
1110/**
1111 * gtk_drop_down_get_show_arrow: (attributes org.gtk.Method.set_property=show-arrow)
1112 * @self: a `GtkDropDown`
1113 *
1114 * Returns whether to show an arrow within the widget.
1115 *
1116 * Returns: %TRUE if an arrow will be shown.
1117 *
1118 * Since: 4.6
1119 */
1120gboolean
1121gtk_drop_down_get_show_arrow (GtkDropDown *self)
1122{
1123 g_return_val_if_fail (GTK_IS_DROP_DOWN (self), FALSE);
1124
1125 return self->show_arrow;
1126}
1127

source code of gtk/gtk/gtkdropdown.c