[gtk/prop-list] wip: Add GtkDropDown
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/prop-list] wip: Add GtkDropDown
- Date: Mon, 9 Dec 2019 20:57:48 +0000 (UTC)
commit 86ab32742230e21458e2f7247995bfcacedfad34
Author: Matthias Clasen <mclasen redhat com>
Date: Sun Dec 8 20:22:06 2019 -0500
wip: Add GtkDropDown
This is a simple drop down control using list models.
gtk/gtk.h | 1 +
gtk/gtkdropdown.c | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++
gtk/gtkdropdown.h | 70 ++++++
gtk/meson.build | 2 +
gtk/ui/gtkdropdown.ui | 71 ++++++
tests/meson.build | 1 +
tests/testdropdown.c | 76 ++++++
7 files changed, 895 insertions(+)
---
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 096fdbca70..78f971db05 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -101,6 +101,7 @@
#include <gtk/gtkdragdest.h>
#include <gtk/gtkdragsource.h>
#include <gtk/gtkdrawingarea.h>
+#include <gtk/gtkdropdown.h>
#include <gtk/gtkeditable.h>
#include <gtk/gtkentry.h>
#include <gtk/gtkentrybuffer.h>
diff --git a/gtk/gtkdropdown.c b/gtk/gtkdropdown.c
new file mode 100644
index 0000000000..443814982e
--- /dev/null
+++ b/gtk/gtkdropdown.c
@@ -0,0 +1,674 @@
+/*
+ * 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 "gtkintl.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 "gtknoselection.h"
+#include "gtkselectionmodel.h"
+#include "gtkstylecontext.h"
+#include "gtkiconprivate.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"
+
+
+struct _GtkDropDown
+{
+ GtkWidget parent_instance;
+
+ GtkListItemFactory *factory;
+ GListModel *model;
+ GListModel *selection;
+ GListModel *filter_model;
+ GListModel *popup_selection;
+
+ GtkWidget *popup;
+ GtkWidget *button;
+
+ GtkWidget *popup_list;
+ GtkWidget *button_stack;
+ GtkWidget *button_item;
+ GtkWidget *button_placeholder;
+ GtkWidget *search_entry;
+
+ gboolean enable_search;
+ GtkExpression *expression;
+};
+
+struct _GtkDropDownClass
+{
+ GtkDropDownClass parent_class;
+};
+
+enum
+{
+ PROP_0,
+ PROP_FACTORY,
+ PROP_MODEL,
+ PROP_SELECTED,
+ PROP_ENABLE_SEARCH,
+ PROP_EXPRESSION,
+
+ N_PROPS
+};
+
+G_DEFINE_TYPE (GtkDropDown, gtk_drop_down, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+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));
+ 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 (GtkSelectionModel *selection,
+ guint position,
+ guint n_items,
+ gpointer data)
+{
+ GtkDropDown *self = data;
+ guint selected;
+ gpointer item;
+ GtkFilter *filter;
+
+ selected = gtk_single_selection_get_selected (GTK_SINGLE_SELECTION (self->selection));
+ item = gtk_single_selection_get_selected_item (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");
+ gtk_list_item_widget_update (GTK_LIST_ITEM_WIDGET (self->button_item), selected, item, FALSE);
+ }
+
+ /* reset the filter so positions are 1-1 */
+ filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->filter_model));
+ gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "");
+ gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (self->popup_selection), selected);
+}
+
+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));
+ gtk_string_filter_set_search (GTK_STRING_FILTER (filter), text);
+}
+
+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_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_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_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_ENABLE_SEARCH:
+ g_value_set_boolean (value, self->enable_search);
+ break;
+
+ case PROP_EXPRESSION:
+ g_value_set_boxed (value, self->expression);
+ 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_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, g_value_get_boxed (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_native_check_resize (GTK_NATIVE (self->popup));
+}
+
+
+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;
+
+ /**
+ * GtkDropDown:factory:
+ *
+ * Factory for populating list items
+ */
+ properties[PROP_FACTORY] =
+ g_param_spec_object ("factory",
+ P_("Factory"),
+ P_("Factory for populating list items"),
+ GTK_TYPE_LIST_ITEM_FACTORY,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkDropDown:model:
+ *
+ * Model for the items displayed
+ */
+ properties[PROP_MODEL] =
+ g_param_spec_object ("model",
+ P_("Model"),
+ P_("Model for the items displayed"),
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SELECTED] =
+ g_param_spec_uint ("selected",
+ P_("Selected"),
+ P_("Position of the selected item"),
+ 0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_ENABLE_SEARCH] =
+ g_param_spec_boolean ("enable-search",
+ P_("Enable search"),
+ P_("Whether to show a search entry in the popup"),
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_EXPRESSION] =
+ g_param_spec_boxed ("expression",
+ P_("Expression"),
+ P_("Expression to determine strings to search for"),
+ GTK_TYPE_EXPRESSION,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkdropdown.ui");
+ 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_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_set_css_name (widget_class, I_("combobox"));
+}
+
+static void
+setup_item (GtkSignalListItemFactory *factory,
+ GtkListItem *list_item,
+ gpointer data)
+{
+ GtkWidget *label;
+
+ label = gtk_label_new (NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_list_item_set_child (list_item, label);
+}
+
+static void
+bind_item (GtkSignalListItemFactory *factory,
+ GtkListItem *list_item,
+ gpointer data)
+{
+ GtkDropDown *self = data;
+ gpointer item;
+ GtkWidget *label;
+ GValue value = G_VALUE_INIT;
+
+ if (self->expression == NULL)
+ {
+ g_critical ("Either GtkDropDown::factory or GtkDropDown::expression must be set");
+ return;
+ }
+
+ item = gtk_list_item_get_item (list_item);
+ label = gtk_list_item_get_child (list_item);
+
+ if (gtk_expression_evaluate (self->expression, item, &value))
+ {
+ gtk_label_set_label (GTK_LABEL (label), g_value_get_string (&value));
+ g_value_unset (&value);
+ }
+}
+
+static void
+gtk_drop_down_init (GtkDropDown *self)
+{
+ GtkListItemFactory *factory;
+
+ g_type_ensure (GTK_TYPE_ICON);
+ g_type_ensure (GTK_TYPE_LIST_ITEM_WIDGET);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ 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);
+
+ gtk_list_view_set_factory (GTK_LIST_VIEW (self->popup_list), factory);
+ gtk_list_item_widget_set_factory (GTK_LIST_ITEM_WIDGET (self->button_item), factory);
+
+ g_object_unref (factory);
+}
+
+/**
+ * gtk_drop_down_new:
+ *
+ * Creates a new empty #GtkDropDown.
+ *
+ * You most likely want to call gtk_drop_down_set_factory() to
+ * set up a way to map its items to widgets and gtk_drop_down_set_model()
+ * to set a model to provide items next.
+ *
+ * Returns: a new #GtkDropDown
+ **/
+GtkWidget *
+gtk_drop_down_new (void)
+{
+ return g_object_new (GTK_TYPE_DROP_DOWN, NULL);
+}
+
+/**
+ * gtk_drop_down_new_with_factory:
+ * @factory: (transfer full): The factory to populate items with
+ *
+ * Creates a new #GtkDropDown that uses the given @factory for
+ * mapping items to widgets.
+ *
+ * You most likely want to call gtk_drop_down_set_model() to set
+ * a model next.
+ *
+ * Returns: a new #GtkDropDown using the given @factory
+ **/
+GtkWidget *
+gtk_drop_down_new_with_factory (GtkListItemFactory *factory)
+{
+ GtkWidget *result;
+
+ g_return_val_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory), NULL);
+
+ result = g_object_new (GTK_TYPE_DROP_DOWN,
+ "factory", factory,
+ NULL);
+
+ g_object_unref (factory);
+
+ return result;
+}
+
+/**
+ * gtk_drop_down_get_model:
+ * @self: a #GtkDropDown
+ *
+ * Gets the model that's currently used to read the items displayed.
+ *
+ * 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:
+ * @self: a #GtkDropDown
+ * @model: (allow-none) (transfer none): the model to use or %NULL for none
+ *
+ * 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_clear_object (&self->selection);
+ g_clear_object (&self->filter_model);
+ g_clear_object (&self->popup_selection);
+ }
+ else
+ {
+ GListModel *filter_model;
+ GListModel *selection;
+ GtkFilter *filter;
+
+ filter = gtk_string_filter_new ();
+ gtk_string_filter_set_match_mode (GTK_STRING_FILTER (filter), GTK_STRING_FILTER_MATCH_MODE_PREFIX);
+ gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), self->expression);
+ filter_model = G_LIST_MODEL (gtk_filter_list_model_new (model, filter));
+ g_set_object (&self->filter_model, filter_model);
+ g_object_unref (filter_model);
+ g_object_unref (filter);
+
+ selection = G_LIST_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);
+
+ if (GTK_IS_SINGLE_SELECTION (model))
+ selection = g_object_ref (model);
+ else
+ selection = G_LIST_MODEL (gtk_single_selection_new (model));
+ g_set_object (&self->selection, selection);
+ g_object_unref (selection);
+
+ g_signal_connect (self->selection, "selection-changed", G_CALLBACK (selection_changed), self);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+}
+
+/**
+ * gtk_drop_down_get_factory:
+ * @self: a #GtkDropDown
+ *
+ * Gets the factory that's currently used to populate list items.
+ *
+ * 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:
+ * @self: a #GtkDropDown
+ * @factory: (allow-none) (transfer none): the factory to use or %NULL for none
+ *
+ * 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_view_set_factory (GTK_LIST_VIEW (self->popup_list), factory);
+ gtk_list_item_widget_set_factory (GTK_LIST_ITEM_WIDGET (self->button_item), factory);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
+}
+
+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);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_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));
+}
+
+void
+gtk_drop_down_set_enable_search (GtkDropDown *self,
+ gboolean enable_search)
+{
+ g_return_if_fail (GTK_IS_DROP_DOWN (self));
+
+ 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_entry, enable_search);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ENABLE_SEARCH]);
+}
+
+gboolean
+gtk_drop_down_get_enable_search (GtkDropDown *self)
+{
+ g_return_val_if_fail (GTK_IS_DROP_DOWN (self), FALSE);
+
+ return self->enable_search;
+}
+
+void
+gtk_drop_down_set_expression (GtkDropDown *self,
+ GtkExpression *expression)
+{
+ GtkFilter *filter;
+
+ 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);
+
+ filter = gtk_filter_list_model_get_filter (GTK_FILTER_LIST_MODEL (self->filter_model));
+ gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), expression);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]);
+}
+
+GtkExpression *
+gtk_drop_down_get_expression (GtkDropDown *self)
+{
+ g_return_val_if_fail (GTK_IS_DROP_DOWN (self), NULL);
+
+ return self->expression;
+}
diff --git a/gtk/gtkdropdown.h b/gtk/gtkdropdown.h
new file mode 100644
index 0000000000..12ede48395
--- /dev/null
+++ b/gtk/gtkdropdown.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2018 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
+ */
+
+#ifndef __GTK_DROP_DOWN_H__
+#define __GTK_DROP_DOWN_H__
+
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkexpression.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_DROP_DOWN (gtk_drop_down_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkDropDown, gtk_drop_down, GTK, DROP_DOWN, GtkWidget)
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_drop_down_new (void);
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_drop_down_new_with_factory (GtkListItemFactory *factory);
+
+GDK_AVAILABLE_IN_ALL
+GListModel * gtk_drop_down_get_model (GtkDropDown *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_drop_down_set_model (GtkDropDown *self,
+ GListModel *model);
+GDK_AVAILABLE_IN_ALL
+void gtk_drop_down_set_factory (GtkDropDown *self,
+ GtkListItemFactory *factory);
+GDK_AVAILABLE_IN_ALL
+GtkListItemFactory *
+ gtk_drop_down_get_factory (GtkDropDown *self);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_drop_down_set_selected (GtkDropDown *self,
+ guint position);
+GDK_AVAILABLE_IN_ALL
+guint gtk_drop_down_get_selected (GtkDropDown *self);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_drop_down_set_enable_search (GtkDropDown *self,
+ gboolean enable_search);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_drop_down_get_enable_search (GtkDropDown *self);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_drop_down_set_expression (GtkDropDown *self,
+ GtkExpression *expression);
+GDK_AVAILABLE_IN_ALL
+GtkExpression * gtk_drop_down_get_expression (GtkDropDown *self);
+
+G_END_DECLS
+
+#endif /* __GTK_DROP_DOWN_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index d0160c857d..d546efbc50 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -229,6 +229,7 @@ gtk_public_sources = files([
'gtkdragdest.c',
'gtkdragsource.c',
'gtkdrawingarea.c',
+ 'gtkdropdown.c',
'gtkeditable.c',
'gtkemojichooser.c',
'gtkemojicompletion.c',
@@ -520,6 +521,7 @@ gtk_public_headers = files([
'gtkdragdest.h',
'gtkdragsource.h',
'gtkdrawingarea.h',
+ 'gtkdropdown.h',
'gtkeditable.h',
'gtkentry.h',
'gtkentrybuffer.h',
diff --git a/gtk/ui/gtkdropdown.ui b/gtk/ui/gtkdropdown.ui
new file mode 100644
index 0000000000..1b17040477
--- /dev/null
+++ b/gtk/ui/gtkdropdown.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+ <template class="GtkDropDown" parent="GtkWidget">
+ <child>
+ <object class="GtkToggleButton" id="button">
+ <signal name="toggled" handler="button_toggled"/>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="GtkStack" id="button_stack">
+ <property name="hhomogeneous">0</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">empty</property>
+ <property name="child">
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">(None)</property>
+ <property name="xalign">0</property>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">item</property>
+ <property name="child">
+ <object class="GtkListItemWidget" id="button_item"/>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkIcon">
+ <property name="css-name">arrow</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <object class="GtkPopover" id="popup">
+ <signal name="closed" handler="popover_closed"/>
+ <property name="relative-to">GtkDropDown</property>
+ <property name="has-arrow">0</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <signal name="search-changed" handler="search_changed"/>
+ <property name="visible">0</property>
+ <property name="placeholder-text" translatable="yes">Search…</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">never</property>
+ <child>
+ <object class="GtkListView" id="popup_list">
+ <signal name="activate" handler="row_activated"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/tests/meson.build b/tests/meson.build
index 2be5d96dae..479ae8db9b 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,5 +1,6 @@
gtk_tests = [
# testname, optional extra sources
+ ['testdropdown'],
['rendernode'],
['rendernode-create-tests'],
['overlayscroll'],
diff --git a/tests/testdropdown.c b/tests/testdropdown.c
new file mode 100644
index 0000000000..842ffda924
--- /dev/null
+++ b/tests/testdropdown.c
@@ -0,0 +1,76 @@
+/* simple.c
+ * Copyright (C) 2017 Red Hat, Inc
+ * Author: Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <gtk/gtk.h>
+
+static char *
+get_family_name (gpointer item)
+{
+ return g_strdup (pango_font_family_get_name (PANGO_FONT_FAMILY (item)));
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window, *button, *box, *spin, *check;
+ GListModel *model;
+ GtkExpression *expression;
+
+ gtk_init ();
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "hello world");
+ gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
+ g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+
+ button = gtk_drop_down_new ();
+ g_object_set (button, "margin", 10, NULL);
+
+ model = G_LIST_MODEL (pango_cairo_font_map_get_default ());
+ gtk_drop_down_set_model (GTK_DROP_DOWN (button), model);
+ gtk_drop_down_set_selected (GTK_DROP_DOWN (button), 0);
+
+ expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
+ 0, NULL,
+ (GCallback)get_family_name,
+ NULL, NULL);
+ gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression);
+ gtk_expression_unref (expression);
+
+ gtk_container_add (GTK_CONTAINER (window), box);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ spin = gtk_spin_button_new_with_range (-1, g_list_model_get_n_items (G_LIST_MODEL (model)), 1);
+ g_object_set (spin, "margin", 10, NULL);
+ g_object_bind_property (button, "selected", spin, "value", G_BINDING_SYNC_CREATE |
G_BINDING_BIDIRECTIONAL);
+ gtk_container_add (GTK_CONTAINER (box), spin);
+
+ check = gtk_check_button_new_with_label ("Enable search");
+ g_object_set (check, "margin", 10, NULL);
+ g_object_bind_property (button, "enable-search", check, "active", G_BINDING_SYNC_CREATE |
G_BINDING_BIDIRECTIONAL);
+ gtk_container_add (GTK_CONTAINER (box), check);
+
+ g_object_unref (model);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]