forked from AuroraMiddleware/gtk
f30e59fed9
GtkSingleSelection will only emit either of those signals if they change. But it is possible that only one of those properties changes, and in those cases we want to only notify for that property changing in the dropdown, too.
1158 lines
34 KiB
C
1158 lines
34 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Matthias Clasen <mclasen@redhat.com>
|
|
*/
|
|
|
|
#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"
|
|
|
|
/**
|
|
* 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 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.
|
|
*
|
|
* `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
|
|
* <object class="GtkDropDown">
|
|
* <property name="model">
|
|
* <object class="GtkStringList">
|
|
* <items>
|
|
* <item translatable="yes">Factory</item>
|
|
* <item translatable="yes">Home</item>
|
|
* <item translatable="yes">Subway</item>
|
|
* </items>
|
|
* </object>
|
|
* </property>
|
|
* </object>
|
|
* ```
|
|
*
|
|
* # 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;
|
|
|
|
GtkListItemFactory *factory;
|
|
GtkListItemFactory *list_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;
|
|
|
|
guint enable_search : 1;
|
|
guint show_arrow : 1;
|
|
};
|
|
|
|
struct _GtkDropDownClass
|
|
{
|
|
GtkWidgetClass parent_class;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_FACTORY,
|
|
PROP_LIST_FACTORY,
|
|
PROP_MODEL,
|
|
PROP_SELECTED,
|
|
PROP_SELECTED_ITEM,
|
|
PROP_ENABLE_SEARCH,
|
|
PROP_EXPRESSION,
|
|
PROP_SHOW_ARROW,
|
|
|
|
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));
|
|
}
|
|
|
|
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_widget_get_position (GTK_LIST_ITEM_WIDGET (self->button_item)))
|
|
{
|
|
gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (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_widget_get_item (GTK_LIST_ITEM_WIDGET (self->button_item)))
|
|
{
|
|
gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (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), GTK_STRING_FILTER_MATCH_MODE_PREFIX);
|
|
}
|
|
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_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_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;
|
|
|
|
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_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;
|
|
|
|
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_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;
|
|
|
|
/**
|
|
* 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: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);
|
|
|
|
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 = gtk_image_new_from_icon_name ("object-select-symbolic");
|
|
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_show (icon);
|
|
g_signal_connect (self, "notify::selected-item",
|
|
G_CALLBACK (selected_item_changed), list_item);
|
|
selected_item_changed (self, NULL, list_item);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_hide (icon);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
gtk_list_item_widget_set_factory (GTK_LIST_ITEM_WIDGET (self->button_item), factory);
|
|
if (self->list_factory == NULL)
|
|
gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), factory);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_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);
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|