/* * Copyright © 2019 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: Matthias Clasen */ #include "config.h" #include "gtkdropdown.h" #include "gtkbuiltiniconprivate.h" #include "gtklistview.h" #include "gtklistitemfactory.h" #include "gtksignallistitemfactory.h" #include "gtklistitemwidgetprivate.h" #include "gtkpopover.h" #include "gtkprivate.h" #include "gtksingleselection.h" #include "gtkfilterlistmodel.h" #include "gtkstringfilter.h" #include "gtkmultifilter.h" #include "gtkwidgetprivate.h" #include "gtknative.h" #include "gtktogglebutton.h" #include "gtkexpression.h" #include "gtkstack.h" #include "gtksearchentry.h" #include "gtklabel.h" #include "gtklistitem.h" #include "gtkbuildable.h" #include "gtkbuilderprivate.h" #include "gtkstringlist.h" #include "gtkbox.h" #include "gtktypebuiltins.h" /** * GtkDropDown: * * `GtkDropDown` is a widget that allows the user to choose an item * from a list of options. * * ![An example GtkDropDown](drop-down.png) * * The `GtkDropDown` displays the [selected][property@Gtk.DropDown:selected] * choice. * * The options are given to `GtkDropDown` in the form of `GListModel` * and how the individual options are represented is determined by * a [class@Gtk.ListItemFactory]. The default factory displays simple strings, * and adds a checkmark to the selected item in the popup. * * To set your own factory, use [method@Gtk.DropDown.set_factory]. It is * possible to use a separate factory for the items in the popup, with * [method@Gtk.DropDown.set_list_factory]. * * `GtkDropDown` knows how to obtain strings from the items in a * [class@Gtk.StringList]; for other models, you have to provide an expression * to find the strings via [method@Gtk.DropDown.set_expression]. * * `GtkDropDown` can optionally allow search in the popup, which is * useful if the list of options is long. To enable the search entry, * use [method@Gtk.DropDown.set_enable_search]. * * Here is a UI definition example for `GtkDropDown` with a simple model: * * ```xml * * * * * Factory * Home * Subway * * * * * ``` * * To learn more about the list widget framework, see the * [overview](section-list-widget.html). * * ## CSS nodes * * `GtkDropDown` has a single CSS node with name dropdown, * with the button and popover nodes as children. * * ## Accessibility * * `GtkDropDown` uses the %GTK_ACCESSIBLE_ROLE_COMBO_BOX role. */ struct _GtkDropDown { GtkWidget parent_instance; gboolean uses_default_factory; gboolean uses_default_list_factory; GtkListItemFactory *factory; GtkListItemFactory *list_factory; GtkListItemFactory *header_factory; GListModel *model; GtkSelectionModel *selection; GListModel *filter_model; GtkSelectionModel *popup_selection; GtkWidget *popup; GtkWidget *button; GtkWidget *arrow; GtkWidget *popup_list; GtkWidget *button_stack; GtkWidget *button_item; GtkWidget *button_placeholder; GtkWidget *search_box; GtkWidget *search_entry; GtkExpression *expression; GtkStringFilterMatchMode search_match_mode; guint enable_search : 1; guint show_arrow : 1; }; struct _GtkDropDownClass { GtkWidgetClass parent_class; }; enum { PROP_0, PROP_FACTORY, PROP_HEADER_FACTORY, PROP_LIST_FACTORY, PROP_MODEL, PROP_SELECTED, PROP_SELECTED_ITEM, PROP_ENABLE_SEARCH, PROP_EXPRESSION, PROP_SHOW_ARROW, PROP_SEARCH_MATCH_MODE, N_PROPS }; enum { ACTIVATE, LAST_SIGNAL }; G_DEFINE_TYPE (GtkDropDown, gtk_drop_down, GTK_TYPE_WIDGET) static GParamSpec *properties[N_PROPS] = { NULL, }; static guint signals[LAST_SIGNAL] = { 0 }; static void button_toggled (GtkWidget *widget, gpointer data) { GtkDropDown *self = data; if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) gtk_popover_popup (GTK_POPOVER (self->popup)); else gtk_popover_popdown (GTK_POPOVER (self->popup)); gtk_accessible_update_state (GTK_ACCESSIBLE (self), GTK_ACCESSIBLE_STATE_EXPANDED, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)), -1); } static void popover_closed (GtkPopover *popover, gpointer data) { GtkDropDown *self = data; gtk_editable_set_text (GTK_EDITABLE (self->search_entry), ""); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE); } static void row_activated (GtkListView *listview, guint position, gpointer data) { GtkDropDown *self = data; GtkFilter *filter; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->button), FALSE); gtk_popover_popdown (GTK_POPOVER (self->popup)); /* reset the filter so positions are 1-1 */ filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->filter_model)); if (GTK_IS_STRING_FILTER (filter)) gtk_string_filter_set_search (GTK_STRING_FILTER (filter), ""); gtk_drop_down_set_selected (self, gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (self->popup_selection))); } static void selection_changed (GtkSingleSelection *selection, GParamSpec *pspec, gpointer data) { GtkDropDown *self = data; guint selected; GtkFilter *filter; selected = gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (self->selection)); if (selected == GTK_INVALID_LIST_POSITION) { gtk_stack_set_visible_child_name (GTK_STACK (self->button_stack), "empty"); } else { gtk_stack_set_visible_child_name (GTK_STACK (self->button_stack), "item"); } if (selected != gtk_list_item_base_get_position (GTK_LIST_ITEM_BASE (self->button_item))) { gtk_list_item_base_update (GTK_LIST_ITEM_BASE (self->button_item), selected, gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (self->selection)), FALSE); } /* reset the filter so positions are 1-1 */ filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->filter_model)); if (GTK_IS_STRING_FILTER (filter)) gtk_string_filter_set_search (GTK_STRING_FILTER (filter), ""); gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (self->popup_selection), selected); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]); } static void selection_item_changed (GtkSingleSelection *selection, GParamSpec *pspec, gpointer data) { GtkDropDown *self = data; gpointer item; item = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (self->selection)); if (item != gtk_list_item_base_get_item (GTK_LIST_ITEM_BASE (self->button_item))) { gtk_list_item_base_update (GTK_LIST_ITEM_BASE (self->button_item), gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (self->selection)), item, FALSE); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_ITEM]); } static void gtk_drop_down_activate (GtkDropDown *self) { gtk_widget_activate (self->button); } static void update_filter (GtkDropDown *self) { if (self->filter_model) { GtkFilter *filter; if (self->expression) { filter = GTK_FILTER (gtk_string_filter_new (gtk_expression_ref (self->expression))); gtk_string_filter_set_match_mode (GTK_STRING_FILTER (filter), self->search_match_mode); } else filter = GTK_FILTER (gtk_every_filter_new ()); gtk_filter_list_model_set_filter (GTK_FILTER_LIST_MODEL (self->filter_model), filter); g_object_unref (filter); } } static void search_changed (GtkSearchEntry *entry, gpointer data) { GtkDropDown *self = data; const char *text; GtkFilter *filter; text = gtk_editable_get_text (GTK_EDITABLE (entry)); filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->filter_model)); if (GTK_IS_STRING_FILTER (filter)) gtk_string_filter_set_search (GTK_STRING_FILTER (filter), text); } static void search_stop (GtkSearchEntry *entry, gpointer data) { GtkDropDown *self = data; GtkFilter *filter; filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->filter_model)); if (GTK_IS_STRING_FILTER (filter)) { if (gtk_string_filter_get_search (GTK_STRING_FILTER (filter))) gtk_string_filter_set_search (GTK_STRING_FILTER (filter), NULL); else gtk_popover_popdown (GTK_POPOVER (self->popup)); } } static void gtk_drop_down_dispose (GObject *object) { GtkDropDown *self = GTK_DROP_DOWN (object); g_clear_pointer (&self->popup, gtk_widget_unparent); g_clear_pointer (&self->button, gtk_widget_unparent); g_clear_object (&self->model); if (self->selection) { g_signal_handlers_disconnect_by_func (self->selection, selection_changed, self); g_signal_handlers_disconnect_by_func (self->selection, selection_item_changed, self); } g_clear_object (&self->filter_model); g_clear_pointer (&self->expression, gtk_expression_unref); g_clear_object (&self->selection); g_clear_object (&self->popup_selection); g_clear_object (&self->factory); g_clear_object (&self->list_factory); g_clear_object (&self->header_factory); G_OBJECT_CLASS (gtk_drop_down_parent_class)->dispose (object); } static void gtk_drop_down_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GtkDropDown *self = GTK_DROP_DOWN (object); switch (property_id) { case PROP_FACTORY: g_value_set_object (value, self->factory); break; case PROP_HEADER_FACTORY: g_value_set_object (value, self->header_factory); break; case PROP_LIST_FACTORY: g_value_set_object (value, self->list_factory); break; case PROP_MODEL: g_value_set_object (value, self->model); break; case PROP_SELECTED: g_value_set_uint (value, gtk_drop_down_get_selected (self)); break; case PROP_SELECTED_ITEM: g_value_set_object (value, gtk_drop_down_get_selected_item (self)); break; case PROP_ENABLE_SEARCH: g_value_set_boolean (value, self->enable_search); break; case PROP_EXPRESSION: gtk_value_set_expression (value, self->expression); break; case PROP_SHOW_ARROW: g_value_set_boolean (value, gtk_drop_down_get_show_arrow (self)); break; case PROP_SEARCH_MATCH_MODE: g_value_set_enum (value, gtk_drop_down_get_search_match_mode (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gtk_drop_down_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GtkDropDown *self = GTK_DROP_DOWN (object); switch (property_id) { case PROP_FACTORY: gtk_drop_down_set_factory (self, g_value_get_object (value)); break; case PROP_HEADER_FACTORY: gtk_drop_down_set_header_factory (self, g_value_get_object (value)); break; case PROP_LIST_FACTORY: gtk_drop_down_set_list_factory (self, g_value_get_object (value)); break; case PROP_MODEL: gtk_drop_down_set_model (self, g_value_get_object (value)); break; case PROP_SELECTED: gtk_drop_down_set_selected (self, g_value_get_uint (value)); break; case PROP_ENABLE_SEARCH: gtk_drop_down_set_enable_search (self, g_value_get_boolean (value)); break; case PROP_EXPRESSION: gtk_drop_down_set_expression (self, gtk_value_get_expression (value)); break; case PROP_SHOW_ARROW: gtk_drop_down_set_show_arrow (self, g_value_get_boolean (value)); break; case PROP_SEARCH_MATCH_MODE: gtk_drop_down_set_search_match_mode (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gtk_drop_down_measure (GtkWidget *widget, GtkOrientation orientation, int size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkDropDown *self = GTK_DROP_DOWN (widget); gtk_widget_measure (self->button, orientation, size, minimum, natural, minimum_baseline, natural_baseline); } static void gtk_drop_down_size_allocate (GtkWidget *widget, int width, int height, int baseline) { GtkDropDown *self = GTK_DROP_DOWN (widget); gtk_widget_size_allocate (self->button, &(GtkAllocation) { 0, 0, width, height }, baseline); gtk_widget_set_size_request (self->popup, width, -1); gtk_widget_queue_resize (self->popup); gtk_popover_present (GTK_POPOVER (self->popup)); } static gboolean gtk_drop_down_focus (GtkWidget *widget, GtkDirectionType direction) { GtkDropDown *self = GTK_DROP_DOWN (widget); if (self->popup && gtk_widget_get_visible (self->popup)) return gtk_widget_child_focus (self->popup, direction); else return gtk_widget_child_focus (self->button, direction); } static gboolean gtk_drop_down_grab_focus (GtkWidget *widget) { GtkDropDown *self = GTK_DROP_DOWN (widget); return gtk_widget_grab_focus (self->button); } static void gtk_drop_down_root (GtkWidget *widget) { GtkDropDown *self = GTK_DROP_DOWN (widget); GTK_WIDGET_CLASS (gtk_drop_down_parent_class)->root (widget); if (self->factory) gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (self->button_item), self->factory); } static void gtk_drop_down_unroot (GtkWidget *widget) { GtkDropDown *self = GTK_DROP_DOWN (widget); if (self->factory) gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (self->button_item), NULL); GTK_WIDGET_CLASS (gtk_drop_down_parent_class)->unroot (widget); } static void gtk_drop_down_class_init (GtkDropDownClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = gtk_drop_down_dispose; gobject_class->get_property = gtk_drop_down_get_property; gobject_class->set_property = gtk_drop_down_set_property; widget_class->measure = gtk_drop_down_measure; widget_class->size_allocate = gtk_drop_down_size_allocate; widget_class->focus = gtk_drop_down_focus; widget_class->grab_focus = gtk_drop_down_grab_focus; widget_class->root = gtk_drop_down_root; widget_class->unroot = gtk_drop_down_unroot; /** * GtkDropDown:factory: (attributes org.gtk.Property.get=gtk_drop_down_get_factory org.gtk.Property.set=gtk_drop_down_set_factory) * * Factory for populating list items. */ properties[PROP_FACTORY] = g_param_spec_object ("factory", NULL, NULL, GTK_TYPE_LIST_ITEM_FACTORY, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:header-factory: (attributes org.gtk.Property.get=gtk_drop_down_get_header_factory org.gtk.Property.set=gtk_drop_down_set_header_factory) * * The factory for creating header widgets for the popup. * * Since: 4.12 */ properties[PROP_HEADER_FACTORY] = g_param_spec_object ("header-factory", NULL, NULL, GTK_TYPE_LIST_ITEM_FACTORY, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:list-factory: (attributes org.gtk.Property.get=gtk_drop_down_get_list_factory org.gtk.Property.set=gtk_drop_down_set_list_factory) * * The factory for populating list items in the popup. * * If this is not set, [property@Gtk.DropDown:factory] is used. */ properties[PROP_LIST_FACTORY] = g_param_spec_object ("list-factory", NULL, NULL, GTK_TYPE_LIST_ITEM_FACTORY, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:model: (attributes org.gtk.Property.get=gtk_drop_down_get_model org.gtk.Property.set=gtk_drop_down_set_model) * * Model for the displayed items. */ properties[PROP_MODEL] = g_param_spec_object ("model", NULL, NULL, G_TYPE_LIST_MODEL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:selected: (attributes org.gtk.Property.get=gtk_drop_down_get_selected org.gtk.Property.set=gtk_drop_down_set_selected) * * The position of the selected item. * * If no item is selected, the property has the value * %GTK_INVALID_LIST_POSITION. */ properties[PROP_SELECTED] = g_param_spec_uint ("selected", NULL, NULL, 0, G_MAXUINT, GTK_INVALID_LIST_POSITION, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:selected-item: (attributes org.gtk.Property.get=gtk_drop_down_get_selected_item) * * The selected item. */ properties[PROP_SELECTED_ITEM] = g_param_spec_object ("selected-item", NULL, NULL, G_TYPE_OBJECT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:enable-search: (attributes org.gtk.Property.get=gtk_drop_down_get_enable_search org.gtk.Property.set=gtk_drop_down_set_enable_search) * * Whether to show a search entry in the popup. * * Note that search requires [property@Gtk.DropDown:expression] * to be set. */ properties[PROP_ENABLE_SEARCH] = g_param_spec_boolean ("enable-search", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:expression: (type GtkExpression) (attributes org.gtk.Property.get=gtk_drop_down_get_expression org.gtk.Property.set=gtk_drop_down_set_expression) * * An expression to evaluate to obtain strings to match against the search * term. * * See [property@Gtk.DropDown:enable-search] for how to enable search. * If [property@Gtk.DropDown:factory] is not set, the expression is also * used to bind strings to labels produced by a default factory. */ properties[PROP_EXPRESSION] = gtk_param_spec_expression ("expression", NULL, NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:show-arrow: (attributes org.gtk.Property.get=gtk_drop_down_get_show_arrow org.gtk.Property.set=gtk_drop_down_set_show_arrow) * * Whether to show an arrow within the GtkDropDown widget. * * Since: 4.6 */ properties[PROP_SHOW_ARROW] = g_param_spec_boolean ("show-arrow", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GtkDropDown:search-match-mode: (attributes org.gtk.Property.get=gtk_drop_down_get_search_match_mode org.gtk.Property.set=gtk_drop_down_set_search_match_mode) * * The match mode for the search filter. * * Since: 4.12 */ properties[PROP_SEARCH_MATCH_MODE] = g_param_spec_enum ("search-match-mode", NULL, NULL, GTK_TYPE_STRING_FILTER_MATCH_MODE, GTK_STRING_FILTER_MATCH_MODE_PREFIX, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, N_PROPS, properties); /** * GtkDropDown::activate: * @widget: the object which received the signal. * * Emitted to when the drop down is activated. * * The `::activate` signal on `GtkDropDown` is an action signal and * emitting it causes the drop down to pop up its dropdown. * * Since: 4.6 */ signals[ACTIVATE] = g_signal_new_class_handler (I_ ("activate"), G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, G_CALLBACK (gtk_drop_down_activate), NULL, NULL, NULL, G_TYPE_NONE, 0); gtk_widget_class_set_activate_signal (widget_class, signals[ACTIVATE]); gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkdropdown.ui"); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, arrow); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, button); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, button_stack); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, button_item); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, popup); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, popup_list); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, search_box); gtk_widget_class_bind_template_child (widget_class, GtkDropDown, search_entry); gtk_widget_class_bind_template_callback (widget_class, row_activated); gtk_widget_class_bind_template_callback (widget_class, button_toggled); gtk_widget_class_bind_template_callback (widget_class, popover_closed); gtk_widget_class_bind_template_callback (widget_class, search_changed); gtk_widget_class_bind_template_callback (widget_class, search_stop); gtk_widget_class_set_css_name (widget_class, I_("dropdown")); gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_COMBO_BOX); } static void setup_item (GtkSignalListItemFactory *factory, GtkListItem *list_item, gpointer data) { GtkWidget *box; GtkWidget *label; GtkWidget *icon; box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); label = gtk_label_new (NULL); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_box_append (GTK_BOX (box), label); icon = g_object_new (GTK_TYPE_IMAGE, "icon-name", "object-select-symbolic", "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, NULL); gtk_box_append (GTK_BOX (box), icon); gtk_list_item_set_child (list_item, box); } static void selected_item_changed (GtkDropDown *self, GParamSpec *pspec, GtkListItem *list_item) { GtkWidget *box; GtkWidget *icon; box = gtk_list_item_get_child (list_item); icon = gtk_widget_get_last_child (box); if (gtk_drop_down_get_selected_item (self) == gtk_list_item_get_item (list_item)) gtk_widget_set_opacity (icon, 1.0); else gtk_widget_set_opacity (icon, 0.0); } static void bind_item (GtkSignalListItemFactory *factory, GtkListItem *list_item, gpointer data) { GtkDropDown *self = data; gpointer item; GtkWidget *box; GtkWidget *label; GtkWidget *icon; GValue value = G_VALUE_INIT; item = gtk_list_item_get_item (list_item); box = gtk_list_item_get_child (list_item); label = gtk_widget_get_first_child (box); icon = gtk_widget_get_last_child (box); if (self->expression && gtk_expression_evaluate (self->expression, item, &value)) { gtk_label_set_label (GTK_LABEL (label), g_value_get_string (&value)); g_value_unset (&value); } else if (GTK_IS_STRING_OBJECT (item)) { const char *string; string = gtk_string_object_get_string (GTK_STRING_OBJECT (item)); gtk_label_set_label (GTK_LABEL (label), string); } else { g_critical ("Either GtkDropDown:factory or GtkDropDown:expression must be set"); } if (gtk_widget_get_ancestor (box, GTK_TYPE_POPOVER) == self->popup) { gtk_widget_set_visible (icon, TRUE); g_signal_connect (self, "notify::selected-item", G_CALLBACK (selected_item_changed), list_item); selected_item_changed (self, NULL, list_item); } else { gtk_widget_set_visible (icon, FALSE); } } static void unbind_item (GtkSignalListItemFactory *factory, GtkListItem *list_item, gpointer data) { GtkDropDown *self = data; g_signal_handlers_disconnect_by_func (self, selected_item_changed, list_item); } static void set_default_factory (GtkDropDown *self) { GtkListItemFactory *factory; factory = gtk_signal_list_item_factory_new (); g_signal_connect (factory, "setup", G_CALLBACK (setup_item), self); g_signal_connect (factory, "bind", G_CALLBACK (bind_item), self); g_signal_connect (factory, "unbind", G_CALLBACK (unbind_item), self); gtk_drop_down_set_factory (self, factory); self->uses_default_factory = TRUE; if (self->uses_default_list_factory) gtk_drop_down_set_list_factory (self, NULL); g_object_unref (factory); } static void gtk_drop_down_init (GtkDropDown *self) { g_type_ensure (GTK_TYPE_BUILTIN_ICON); g_type_ensure (GTK_TYPE_LIST_ITEM_WIDGET); gtk_widget_init_template (GTK_WIDGET (self)); self->show_arrow = gtk_widget_get_visible (self->arrow); set_default_factory (self); self->search_match_mode = GTK_STRING_FILTER_MATCH_MODE_PREFIX; } /** * gtk_drop_down_new: * @model: (transfer full) (nullable): the model to use * @expression: (transfer full) (nullable): the expression to use * * Creates a new `GtkDropDown`. * * You may want to call [method@Gtk.DropDown.set_factory] * to set up a way to map its items to widgets. * * Returns: a new `GtkDropDown` */ GtkWidget * gtk_drop_down_new (GListModel *model, GtkExpression *expression) { GtkWidget *self; self = g_object_new (GTK_TYPE_DROP_DOWN, "model", model, "expression", expression, NULL); /* we're consuming the references */ g_clear_object (&model); g_clear_pointer (&expression, gtk_expression_unref); return self; } /** * gtk_drop_down_new_from_strings: * @strings: (array zero-terminated=1): The strings to put in the dropdown * * Creates a new `GtkDropDown` that is populated with * the strings. * * Returns: a new `GtkDropDown` */ GtkWidget * gtk_drop_down_new_from_strings (const char * const *strings) { return gtk_drop_down_new (G_LIST_MODEL (gtk_string_list_new (strings)), NULL); } /** * gtk_drop_down_get_model: (attributes org.gtk.Method.get_property=model) * @self: a `GtkDropDown` * * Gets the model that provides the displayed items. * * Returns: (nullable) (transfer none): The model in use */ GListModel * gtk_drop_down_get_model (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL); return self->model; } /** * gtk_drop_down_set_model: (attributes org.gtk.Method.set_property=model) * @self: a `GtkDropDown` * @model: (nullable) (transfer none): the model to use * * Sets the `GListModel` to use. */ void gtk_drop_down_set_model (GtkDropDown *self, GListModel *model) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model)); if (!g_set_object (&self->model, model)) return; if (model == NULL) { gtk_list_view_set_model (GTK_LIST_VIEW (self->popup_list), NULL); if (self->selection) { g_signal_handlers_disconnect_by_func (self->selection, selection_changed, self); g_signal_handlers_disconnect_by_func (self->selection, selection_item_changed, self); } g_clear_object (&self->selection); g_clear_object (&self->filter_model); g_clear_object (&self->popup_selection); } else { GListModel *filter_model; GtkSelectionModel *selection; filter_model = G_LIST_MODEL (gtk_filter_list_model_new (g_object_ref (model), NULL)); g_set_object (&self->filter_model, filter_model); update_filter (self); selection = GTK_SELECTION_MODEL (gtk_single_selection_new (filter_model)); g_set_object (&self->popup_selection, selection); gtk_list_view_set_model (GTK_LIST_VIEW (self->popup_list), selection); g_object_unref (selection); selection = GTK_SELECTION_MODEL (gtk_single_selection_new (g_object_ref (model))); g_set_object (&self->selection, selection); g_object_unref (selection); g_signal_connect (self->selection, "notify::selected", G_CALLBACK (selection_changed), self); g_signal_connect (self->selection, "notify::selected-item", G_CALLBACK (selection_item_changed), self); selection_changed (GTK_SINGLE_SELECTION (self->selection), NULL, self); selection_item_changed (GTK_SINGLE_SELECTION (self->selection), NULL, self); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); } /** * gtk_drop_down_get_factory: (attributes org.gtk.Method.get_property=factory) * @self: a `GtkDropDown` * * Gets the factory that's currently used to populate list items. * * The factory returned by this function is always used for the * item in the button. It is also used for items in the popup * if [property@Gtk.DropDown:list-factory] is not set. * * Returns: (nullable) (transfer none): The factory in use */ GtkListItemFactory * gtk_drop_down_get_factory (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL); return self->factory; } /** * gtk_drop_down_set_factory: (attributes org.gtk.Method.set_property=factory) * @self: a `GtkDropDown` * @factory: (nullable) (transfer none): the factory to use * * Sets the `GtkListItemFactory` to use for populating list items. */ void gtk_drop_down_set_factory (GtkDropDown *self, GtkListItemFactory *factory) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory)); if (!g_set_object (&self->factory, factory)) return; if (gtk_widget_get_root (GTK_WIDGET (self))) gtk_list_factory_widget_set_factory (GTK_LIST_FACTORY_WIDGET (self->button_item), factory); if (self->list_factory == NULL) { gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), factory); self->uses_default_list_factory = TRUE; } self->uses_default_factory = factory != NULL; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]); } /** * gtk_drop_down_get_header_factory: (attributes org.gtk.Method.get_property=header-factory) * @self: a `GtkDropDown` * * Gets the factory that's currently used to create header widgets for the popup. * * Returns: (nullable) (transfer none): The factory in use * * Since: 4.12 */ GtkListItemFactory * gtk_drop_down_get_header_factory (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL); return self->header_factory; } /** * gtk_drop_down_set_header_factory: (attributes org.gtk.Method.set_property=header-factory) * @self: a `GtkDropDown` * @factory: (nullable) (transfer none): the factory to use * * Sets the `GtkListItemFactory` to use for creating header widgets for the popup. * * Since: 4.12 */ void gtk_drop_down_set_header_factory (GtkDropDown *self, GtkListItemFactory *factory) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory)); if (!g_set_object (&self->header_factory, factory)) return; gtk_list_view_set_header_factory (GTK_LIST_VIEW (self->popup_list), self->header_factory); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEADER_FACTORY]); } /** * gtk_drop_down_get_list_factory: (attributes org.gtk.Method.get_property=list-factory) * @self: a `GtkDropDown` * * Gets the factory that's currently used to populate list items in the popup. * * Returns: (nullable) (transfer none): The factory in use */ GtkListItemFactory * gtk_drop_down_get_list_factory (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL); return self->list_factory; } /** * gtk_drop_down_set_list_factory: (attributes org.gtk.Method.set_property=list-factory) * @self: a `GtkDropDown` * @factory: (nullable) (transfer none): the factory to use * * Sets the `GtkListItemFactory` to use for populating list items in the popup. */ void gtk_drop_down_set_list_factory (GtkDropDown *self, GtkListItemFactory *factory) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory)); if (!g_set_object (&self->list_factory, factory)) return; if (self->list_factory != NULL) gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), self->list_factory); else gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), self->factory); self->uses_default_list_factory = factory != NULL; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LIST_FACTORY]); } /** * gtk_drop_down_set_selected: (attributes org.gtk.Method.set_property=selected) * @self: a `GtkDropDown` * @position: the position of the item to select, or %GTK_INVALID_LIST_POSITION * * Selects the item at the given position. */ void gtk_drop_down_set_selected (GtkDropDown *self, guint position) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); if (self->selection == NULL) return; if (gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (self->selection)) == position) return; gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (self->selection), position); } /** * gtk_drop_down_get_selected: (attributes org.gtk.Method.get_property=selected) * @self: a `GtkDropDown` * * Gets the position of the selected item. * * Returns: the position of the selected item, or %GTK_INVALID_LIST_POSITION * if not item is selected */ guint gtk_drop_down_get_selected (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), GTK_INVALID_LIST_POSITION); if (self->selection == NULL) return GTK_INVALID_LIST_POSITION; return gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (self->selection)); } /** * gtk_drop_down_get_selected_item: (attributes org.gtk.Method.get_property=selected-item) * @self: a `GtkDropDown` * * Gets the selected item. If no item is selected, %NULL is returned. * * Returns: (transfer none) (type GObject) (nullable): The selected item */ gpointer gtk_drop_down_get_selected_item (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL); if (self->selection == NULL) return NULL; return gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (self->selection)); } /** * gtk_drop_down_set_enable_search: (attributes org.gtk.Method.set_property=enable-search) * @self: a `GtkDropDown` * @enable_search: whether to enable search * * Sets whether a search entry will be shown in the popup that * allows to search for items in the list. * * Note that [property@Gtk.DropDown:expression] must be set for * search to work. */ void gtk_drop_down_set_enable_search (GtkDropDown *self, gboolean enable_search) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); enable_search = !!enable_search; if (self->enable_search == enable_search) return; self->enable_search = enable_search; gtk_editable_set_text (GTK_EDITABLE (self->search_entry), ""); gtk_widget_set_visible (self->search_box, enable_search); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_SEARCH]); } /** * gtk_drop_down_get_enable_search: (attributes org.gtk.Method.set_property=enable-search) * @self: a `GtkDropDown` * * Returns whether search is enabled. * * Returns: %TRUE if the popup includes a search entry */ gboolean gtk_drop_down_get_enable_search (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), FALSE); return self->enable_search; } /** * gtk_drop_down_set_expression: (attributes org.gtk.Method.set_property=expression) * @self: a `GtkDropDown` * @expression: (nullable): a `GtkExpression` * * Sets the expression that gets evaluated to obtain strings from items. * * This is used for search in the popup. The expression must have * a value type of %G_TYPE_STRING. */ void gtk_drop_down_set_expression (GtkDropDown *self, GtkExpression *expression) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); g_return_if_fail (expression == NULL || gtk_expression_get_value_type (expression) == G_TYPE_STRING); if (self->expression == expression) return; if (self->expression) gtk_expression_unref (self->expression); self->expression = expression; if (self->expression) gtk_expression_ref (self->expression); if (self->uses_default_factory) set_default_factory (self); update_filter (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]); } /** * gtk_drop_down_get_expression: (attributes org.gtk.Method.get_property=expression) * @self: a `GtkDropDown` * * Gets the expression set that is used to obtain strings from items. * * See [method@Gtk.DropDown.set_expression]. * * Returns: (nullable) (transfer none): a `GtkExpression` */ GtkExpression * gtk_drop_down_get_expression (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL); return self->expression; } /** * gtk_drop_down_set_show_arrow: (attributes org.gtk.Method.set_property=show-arrow) * @self: a `GtkDropDown` * @show_arrow: whether to show an arrow within the widget * * Sets whether an arrow will be displayed within the widget. * * Since: 4.6 */ void gtk_drop_down_set_show_arrow (GtkDropDown *self, gboolean show_arrow) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); show_arrow = !!show_arrow; if (self->show_arrow == show_arrow) return; self->show_arrow = show_arrow; gtk_widget_set_visible (self->arrow, show_arrow); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_ARROW]); } /** * gtk_drop_down_get_show_arrow: (attributes org.gtk.Method.set_property=show-arrow) * @self: a `GtkDropDown` * * Returns whether to show an arrow within the widget. * * Returns: %TRUE if an arrow will be shown. * * Since: 4.6 */ gboolean gtk_drop_down_get_show_arrow (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), FALSE); return self->show_arrow; } /** * gtk_drop_down_set_search_match_mode: (attributes org.gtk.Method.set_property=search-match-mode) * @self: a `GtkDropDown` * @search_match_mode: the new match mode * * Sets the match mode for the search filter. * * Since: 4.12 */ void gtk_drop_down_set_search_match_mode (GtkDropDown *self, GtkStringFilterMatchMode search_match_mode) { g_return_if_fail (GTK_IS_DROP_DOWN (self)); if (self->search_match_mode == search_match_mode) return; self->search_match_mode = search_match_mode; update_filter (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_MATCH_MODE]); } /** * gtk_drop_down_get_search_match_mode: (attributes org.gtk.Method.get_property=search-match-mode) * @self: a `GtkDropDown` * * Returns the match mode that the search filter is using. * * Returns: the match mode of the search filter * * Since: 4.12 */ GtkStringFilterMatchMode gtk_drop_down_get_search_match_mode (GtkDropDown *self) { g_return_val_if_fail (GTK_IS_DROP_DOWN (self), GTK_STRING_FILTER_MATCH_MODE_PREFIX); return self->search_match_mode; }