[gtk+/wip/combo: 1/5] A new combo box



commit 2662457ab41b4f6afda818f7b5bc26a6594b7da2
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Dec 25 07:02:34 2014 -0500

    A new combo box
    
    Add a new, simple combo box. The feature set of this is similar
    to GtkComboBoxText, but it is using a popover and supports search.
    
    
https://raw.githubusercontent.com/gnome-design-team/gnome-mockups/master/theming/widgets/combobox-replacements.png

 gtk/Makefile.am         |    6 +-
 gtk/gtk.h               |    1 +
 gtk/gtkcombo.c          | 1189 +++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkcombo.h          |   77 +++
 gtk/ui/gtkcombo.ui      |  232 +++++++++
 gtk/ui/gtkcombo.ui.h    |    4 +
 gtk/ui/gtkcomborow.ui   |   28 ++
 po/POTFILES.in          |    2 +
 8 files changed, 1538 insertions(+), 1 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index d897df8..d38643f 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -157,6 +157,7 @@ gtk_public_h_sources =              \
        gtkcolorchooserwidget.h \
        gtkcolorchooserdialog.h \
        gtkcolorutils.h         \
+       gtkcombo.h              \
        gtkcombobox.h           \
        gtkcomboboxtext.h       \
        gtkcontainer.h          \
@@ -588,6 +589,7 @@ gtk_base_c_sources =                \
        gtkcolorscale.c         \
        gtkcolorswatch.c        \
        gtkcolorutils.c         \
+       gtkcombo.c              \
        gtkcombobox.c           \
        gtkcomboboxtext.c       \
        gtkcontainer.c          \
@@ -1047,7 +1049,9 @@ templates =                               \
        ui/gtkassistant.ui              \
        ui/gtkcolorchooserdialog.ui     \
        ui/gtkcoloreditor.ui            \
-       ui/gtkdialog.ui         \
+       ui/gtkcombo.ui                  \
+       ui/gtkcomborow.ui               \
+       ui/gtkdialog.ui                 \
        ui/gtkfilechooserbutton.ui      \
        ui/gtkfilechooserwidget.ui      \
        ui/gtkfilechooserdialog.ui      \
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 131de05..e980709 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -76,6 +76,7 @@
 #include <gtk/gtkcolorchooserdialog.h>
 #include <gtk/gtkcolorchooserwidget.h>
 #include <gtk/gtkcolorutils.h>
+#include <gtk/gtkcombo.h>
 #include <gtk/gtkcombobox.h>
 #include <gtk/gtkcomboboxtext.h>
 #include <gtk/gtkcontainer.h>
diff --git a/gtk/gtkcombo.c b/gtk/gtkcombo.c
new file mode 100644
index 0000000..2462f3f
--- /dev/null
+++ b/gtk/gtkcombo.c
@@ -0,0 +1,1189 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2014 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 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/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gtkcombo.h"
+
+#include "gtkbin.h"
+#include "gtkbox.h"
+#include "gtkbuildable.h"
+#include "gtkbuilderprivate.h"
+#include "gtkbutton.h"
+#include "gtkentry.h"
+#include "gtklabel.h"
+#include "gtklistbox.h"
+#include "gtkimage.h"
+#include "gtkscrolledwindow.h"
+#include "gtksearchbar.h"
+#include "gtksearchentry.h"
+#include "gtkseparator.h"
+#include "gtkstack.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+
+#define GTK_TYPE_COMBO_ROW    (gtk_combo_row_get_type ())
+#define GTK_COMBO_ROW(obj)    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_COMBO_ROW, GtkComboRow))
+#define GTK_IS_COMBO_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_COMBO_ROW))
+
+typedef struct
+{
+  GtkListBoxRow row;
+
+  gchar *id;
+  gchar *text;
+  gchar *sort;
+  gboolean active;
+
+  GtkWidget *label;
+  GtkWidget *check;
+} GtkComboRow;
+
+enum {
+  ROW_PROP_ACTIVE = 1,
+  ROW_PROP_ID,
+  ROW_PROP_SORT,
+  ROW_PROP_TEXT
+};
+
+typedef GtkListBoxRowClass GtkComboRowClass;
+
+G_DEFINE_TYPE (GtkComboRow, gtk_combo_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+gtk_combo_row_init (GtkComboRow *row)
+{
+  gtk_widget_init_template (GTK_WIDGET (row));
+}
+
+static void
+gtk_combo_row_finalize (GObject *object)
+{
+  GtkComboRow *row = (GtkComboRow *)object;
+
+  g_free (row->id);
+  g_free (row->text);
+  g_free (row->sort);
+
+  G_OBJECT_CLASS (gtk_combo_row_parent_class)->finalize (object);
+}
+
+static void
+gtk_combo_row_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GtkComboRow *row = (GtkComboRow *)object;
+
+  switch (prop_id)
+    {
+    case ROW_PROP_ACTIVE:
+      row->active = g_value_get_boolean (value);
+      break;
+    case ROW_PROP_ID:
+      g_free (row->id);
+      row->id = g_value_dup_string (value);
+      break;
+    case ROW_PROP_TEXT:
+      g_free (row->text);
+      row->text = g_value_dup_string (value);
+      break;
+    case ROW_PROP_SORT:
+      g_free (row->sort);
+      row->sort = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_combo_row_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GtkComboRow *row = (GtkComboRow *)object;
+
+  switch (prop_id)
+    {
+    case ROW_PROP_ACTIVE:
+      g_value_set_boolean (value, row->active);
+      break;
+    case ROW_PROP_ID:
+      g_value_set_string (value, row->id);
+      break;
+    case ROW_PROP_TEXT:
+      g_value_set_string (value, row->text);
+      break;
+    case ROW_PROP_SORT:
+      g_value_set_string (value, row->sort);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_combo_row_class_init (GtkComboRowClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+  object_class->finalize = gtk_combo_row_finalize;
+  object_class->set_property = gtk_combo_row_set_property;
+  object_class->get_property = gtk_combo_row_get_property;
+
+  g_object_class_install_property (object_class,
+                                   ROW_PROP_ACTIVE,
+                                   g_param_spec_boolean ("active", P_("Active"), P_("Active"),
+                                                         FALSE,
+                                                         GTK_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+                                   ROW_PROP_ID,
+                                   g_param_spec_string ("id", P_("ID"), P_("ID"),
+                                                        NULL,
+                                                        GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
+  g_object_class_install_property (object_class,
+                                   ROW_PROP_TEXT,
+                                   g_param_spec_string ("text", P_("Text"), P_("Text"),
+                                                        NULL,
+                                                        GTK_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+                                   ROW_PROP_SORT,
+                                   g_param_spec_string ("sort", P_("Sort"), P_("Sort"),
+                                                        NULL,
+                                                        GTK_PARAM_READWRITE));
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkcomborow.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, GtkComboRow, label);
+  gtk_widget_class_bind_template_child (widget_class, GtkComboRow, check);
+}
+
+static GtkWidget *
+gtk_combo_row_new (const gchar *id,
+                   const gchar *sort,
+                   const gchar *text)
+{
+  return g_object_new (GTK_TYPE_COMBO_ROW,
+                       "id", id,
+                       "sort", sort,
+                       "text", text,
+                       NULL);
+}
+
+static void
+gtk_combo_row_set_text (GtkComboRow *row,
+                        const gchar *text)
+{
+  g_free (row->text);
+  row->text = g_strdup (text);
+
+  g_object_notify (G_OBJECT (row), "text");
+}
+
+static void
+gtk_combo_row_set_sort (GtkComboRow *row,
+                        const gchar *sort)
+{
+  g_free (row->sort);
+  row->sort = g_strdup (sort);
+
+  g_object_notify (G_OBJECT (row), "sort");
+}
+
+static const gchar *
+gtk_combo_row_get_id (GtkComboRow *row)
+{
+  return row->id;
+}
+
+static const gchar *
+gtk_combo_row_get_text (GtkComboRow *row)
+{
+  return row->text ? row->text : row->id;
+}
+
+static const gchar *
+gtk_combo_row_get_sort (GtkComboRow *row)
+{
+  return row->sort ? row->sort : gtk_combo_row_get_text (row);
+}
+
+
+/***/
+
+
+static void     list_row_activated   (GtkListBox     *list,
+                                      GtkListBoxRow  *row,
+                                      GtkCombo       *combo);
+static void     search_changed       (GtkSearchEntry *entry,
+                                      GtkCombo       *combo);
+static void     custom_row_activated (GtkListBox     *list,
+                                      GtkListBoxRow  *row,
+                                      GtkCombo       *combo);
+static void     custom_entry_done    (GtkWidget      *widget,
+                                      GtkCombo       *combo);
+static void     custom_entry_changed (GObject        *entry,
+                                      GParamSpec     *pspec,
+                                      GtkCombo       *combo);
+static gboolean popover_key_press    (GtkWidget      *widget,
+                                      GdkEvent       *event,
+                                      GtkCombo       *combo);
+static gboolean button_key_press     (GtkWidget      *widget,
+                                      GdkEvent       *event,
+                                      GtkCombo       *combo);
+static void     reset_popover        (GtkWidget      *popover,
+                                      GtkCombo       *combo);
+static void     collapse_list        (GtkCombo       *combo);
+static void     expand_list          (GtkCombo       *combo);
+static void     list_header_func     (GtkListBoxRow  *row,
+                                      GtkListBoxRow  *before,
+                                      gpointer        data);
+static gboolean list_filter_func     (GtkListBoxRow  *row,
+                                      gpointer        data);
+static gint     list_sort_func       (GtkListBoxRow  *row1,
+                                      GtkListBoxRow  *row2,
+                                      gpointer        data);
+static void     custom_header_func   (GtkListBoxRow  *row,
+                                      GtkListBoxRow  *before,
+                                      gpointer        data);
+
+static void     gtk_combo_buildable_init (GtkBuildableIface *iface);
+
+
+struct _GtkCombo
+{
+  GtkBin parent;
+
+  const gchar *active;
+  gchar *placeholder;
+  gboolean allow_custom;
+
+  GtkWidget *active_label;
+  GtkWidget *popover;
+  GtkWidget *scrolled_window;
+  GtkWidget *list;
+  GtkWidget *search_revealer;
+  GtkWidget *search_entry;
+  GtkWidget *stack;
+  GtkWidget *show_more;
+  GtkWidget *add_custom;
+  GtkWidget *back_to_list;
+  GtkWidget *custom_entry;
+  GtkWidget *custom_button;
+  GtkWidget *custom;
+};
+
+struct _GtkComboClass
+{
+  GtkBinClass parent_class;
+};
+
+enum {
+  PROP_ACTIVE = 1,
+  PROP_PLACEHOLDER,
+  PROP_ALLOW_CUSTOM
+};
+
+static GtkBuildableIface *buildable_parent_iface = NULL;
+
+G_DEFINE_TYPE_WITH_CODE (GtkCombo, gtk_combo, GTK_TYPE_BIN,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+                                                gtk_combo_buildable_init))
+
+static void
+gtk_combo_init (GtkCombo *combo)
+{
+  gtk_widget_init_template (GTK_WIDGET (combo));
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (combo->list), list_header_func, combo, NULL);
+  gtk_list_box_set_filter_func (GTK_LIST_BOX (combo->list), list_filter_func, combo, NULL);
+  gtk_list_box_set_sort_func (GTK_LIST_BOX (combo->list), list_sort_func, combo, NULL);
+
+  gtk_list_box_set_header_func (GTK_LIST_BOX (combo->custom), custom_header_func, combo, NULL);
+
+  reset_popover (combo->popover, combo);
+}
+
+static void
+gtk_combo_finalize (GObject *object)
+{
+  GtkCombo *combo = GTK_COMBO (object);
+
+  g_free (combo->placeholder);
+
+  G_OBJECT_CLASS (gtk_combo_parent_class)->finalize (object);
+}
+
+static void
+gtk_combo_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  GtkCombo *combo = (GtkCombo *)object;
+
+  switch (prop_id)
+    {
+    case PROP_ACTIVE:
+      gtk_combo_set_active (combo, g_value_get_string (value));
+      break;
+    case PROP_PLACEHOLDER:
+      gtk_combo_set_placeholder (combo, g_value_get_string (value));
+      break;
+    case PROP_ALLOW_CUSTOM:
+      gtk_combo_set_allow_custom (combo, g_value_get_boolean (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_combo_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  GtkCombo *combo = (GtkCombo *)object;
+
+  switch (prop_id)
+    {
+    case PROP_ACTIVE:
+      g_value_set_string (value, gtk_combo_get_active (combo));
+      break;
+    case PROP_PLACEHOLDER:
+      g_value_set_string (value, gtk_combo_get_placeholder (combo));
+      break;
+    case PROP_ALLOW_CUSTOM:
+      g_value_set_boolean (value, gtk_combo_get_allow_custom (combo));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gtk_combo_class_init (GtkComboClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+  object_class->finalize = gtk_combo_finalize;
+  object_class->set_property = gtk_combo_set_property;
+  object_class->get_property = gtk_combo_get_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_ACTIVE,
+                                   g_param_spec_string ("active", P_("Active"), P_("Active"),
+                                                        NULL,
+                                                        GTK_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+                                   PROP_PLACEHOLDER,
+                                   g_param_spec_string ("placeholder", P_("Placeholder"), P_("Placeholder"),
+                                                        NULL,
+                                                        GTK_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+                                   PROP_ALLOW_CUSTOM,
+                                   g_param_spec_boolean ("allow-custom", P_("Allow custom"), P_("Allow 
custom"),
+                                                         FALSE,
+                                                         GTK_PARAM_READWRITE));
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkcombo.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, active_label);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, popover);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, scrolled_window);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, list);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, search_revealer);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, search_entry);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, show_more);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, add_custom);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, stack);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, back_to_list);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, custom_entry);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, custom_button);
+  gtk_widget_class_bind_template_child (widget_class, GtkCombo, custom);
+  gtk_widget_class_bind_template_callback (widget_class, list_row_activated);
+  gtk_widget_class_bind_template_callback (widget_class, search_changed);
+  gtk_widget_class_bind_template_callback (widget_class, reset_popover);
+  gtk_widget_class_bind_template_callback (widget_class, custom_row_activated);
+  gtk_widget_class_bind_template_callback (widget_class, custom_entry_done);
+  gtk_widget_class_bind_template_callback (widget_class, custom_entry_changed);
+  gtk_widget_class_bind_template_callback (widget_class, popover_key_press);
+  gtk_widget_class_bind_template_callback (widget_class, button_key_press);
+}
+
+
+/***/
+
+
+typedef struct {
+  GtkBuilder    *builder;
+  GObject       *object;
+  const gchar   *domain;
+  gchar         *id;
+  gchar         *sort;
+
+  GString       *string;
+
+  gchar         *context;
+  guint          translatable : 1;
+  guint          is_text      : 1;
+} ItemParserData;
+
+static void
+item_start_element (GMarkupParseContext *context,
+                    const gchar         *element_name,
+                    const gchar        **names,
+                    const gchar        **values,
+                    gpointer             user_data,
+                    GError             **error)
+{
+  ItemParserData *data = (ItemParserData*)user_data;
+  guint i;
+
+  if (strcmp (element_name, "item") == 0)
+    {
+      data->is_text = TRUE;
+      for (i = 0; names[i]; i++)
+        {
+          if (strcmp (names[i], "translatable") == 0)
+            {
+              gboolean bval;
+
+              if (!_gtk_builder_boolean_from_string (values[i], &bval, error))
+                return;
+
+              data->translatable = bval;
+            }
+          else if (strcmp (names[i], "comments") == 0)
+            {
+              /* do nothing, comments are for translators */
+            }
+          else if (strcmp (names[i], "context") == 0)
+            data->context = g_strdup (values[i]);
+          else if (strcmp (names[i], "id") == 0)
+            data->id = g_strdup (values[i]);
+          else if (strcmp (names[i], "sort") == 0)
+            data->sort = g_strdup (values[i]);
+          else
+            g_warning ("Unknown custom combo item attribute: %s", names[i]);
+        }
+    }
+}
+
+static void
+item_text (GMarkupParseContext *context,
+           const gchar         *text,
+           gsize                text_len,
+           gpointer             user_data,
+           GError             **error)
+{
+  ItemParserData *data = (ItemParserData*)user_data;
+
+  if (data->is_text)
+    g_string_append_len (data->string, text, text_len);
+}
+
+static void
+item_end_element (GMarkupParseContext *context,
+                  const gchar         *element_name,
+                  gpointer             user_data,
+                  GError             **error)
+{
+  ItemParserData *data = (ItemParserData*)user_data;
+
+  if (data->string->len)
+    {
+      if (data->translatable)
+        {
+          const gchar *translated;
+
+          translated = _gtk_builder_parser_translate (data->domain,
+                                                      data->context,
+                                                      data->string->str);
+          g_string_assign (data->string, translated);
+        }
+
+      gtk_combo_add_item (GTK_COMBO (data->object), data->id, data->string->str, data->sort);
+    }
+
+  data->translatable = FALSE;
+  g_string_set_size (data->string, 0);
+  g_free (data->context);
+  data->context = NULL;
+  g_free (data->id);
+  data->id = NULL;
+  data->is_text = FALSE;
+}
+
+static const GMarkupParser item_parser =
+{
+  item_start_element,
+  item_end_element,
+  item_text
+};
+
+static gboolean
+gtk_combo_buildable_custom_tag_start (GtkBuildable  *buildable,
+                                      GtkBuilder    *builder,
+                                      GObject       *child,
+                                      const gchar   *tagname,
+                                      GMarkupParser *parser,
+                                      gpointer      *data)
+{
+  if (buildable_parent_iface->custom_tag_start (buildable, builder, child,
+                                                tagname, parser, data))
+    return TRUE;
+
+  if (strcmp (tagname, "items") == 0)
+    {
+      ItemParserData *parser_data;
+
+      parser_data = g_slice_new0 (ItemParserData);
+      parser_data->builder = g_object_ref (builder);
+      parser_data->object = g_object_ref (buildable);
+      parser_data->domain = gtk_builder_get_translation_domain (builder);
+      parser_data->string = g_string_new ("");
+      *parser = item_parser;
+      *data = parser_data;
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+gtk_combo_buildable_custom_finished (GtkBuildable *buildable,
+                                     GtkBuilder   *builder,
+                                     GObject      *child,
+                                     const gchar  *tagname,
+                                     gpointer      user_data)
+{
+  ItemParserData *data;
+
+  buildable_parent_iface->custom_finished (buildable, builder, child,
+                                           tagname, user_data);
+
+  if (strcmp (tagname, "items") == 0)
+    {
+      data = (ItemParserData*)user_data;
+
+      g_object_unref (data->object);
+      g_object_unref (data->builder);
+      g_string_free (data->string, TRUE);
+      g_slice_free (ItemParserData, data);
+    }
+}
+
+static void
+gtk_combo_buildable_init (GtkBuildableIface *iface)
+{
+  buildable_parent_iface = g_type_interface_peek_parent (iface);
+
+  iface->custom_tag_start = gtk_combo_buildable_custom_tag_start;
+  iface->custom_finished = gtk_combo_buildable_custom_finished;
+}
+
+
+/***/
+
+
+static void
+list_header_func (GtkListBoxRow *row,
+                  GtkListBoxRow *before,
+                  gpointer       data)
+{
+  GtkWidget *ret = NULL;
+
+  if (before && !gtk_list_box_row_get_header (row))
+    {
+      ret = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+      gtk_list_box_row_set_header (row, ret);
+    }
+}
+
+static gboolean
+list_filter_func (GtkListBoxRow *row,
+                  gpointer       data)
+{
+  GtkCombo *combo = data;
+  const gchar *text;
+  gchar *row_text;
+  gchar *search_text;
+  gboolean ret;
+
+  if (!gtk_revealer_get_child_revealed (GTK_REVEALER (combo->search_revealer)))
+    return TRUE;
+
+  text = gtk_entry_get_text (GTK_ENTRY (combo->search_entry));
+  if (text[0] == '\0')
+    return TRUE;
+
+  if (!GTK_IS_COMBO_ROW (row))
+    return TRUE;
+
+  row_text = g_utf8_strdown (gtk_combo_row_get_text (GTK_COMBO_ROW (row)), -1);
+  search_text = g_utf8_strdown (text, -1);
+  ret = strstr (row_text, search_text) != NULL;
+  g_free (row_text);
+  g_free (search_text);
+
+  return ret;
+}
+
+static gint
+list_sort_func (GtkListBoxRow *row1,
+                GtkListBoxRow *row2,
+                gpointer       data)
+{
+  GtkCombo *combo = data;
+
+  if (row1 == row2)
+    return 0;
+
+  if (GTK_IS_COMBO_ROW (row1) && GTK_IS_COMBO_ROW (row2))
+    {
+      const gchar *sort1;
+      const gchar *sort2;
+
+      sort1 = gtk_combo_row_get_sort (GTK_COMBO_ROW (row1));
+      sort2 = gtk_combo_row_get_sort (GTK_COMBO_ROW (row2));
+
+      return g_strcmp0 (sort1, sort2);
+    }
+
+  if ((GtkWidget*)row1 == combo->add_custom)
+    return -1;
+
+  if ((GtkWidget*)row1 == combo->show_more)
+    return 1;
+
+  if ((GtkWidget*)row2 == combo->add_custom)
+    return 1;
+
+  if ((GtkWidget*)row2 == combo->show_more)
+    return -1;
+
+  return 0;
+}
+
+static void
+custom_header_func (GtkListBoxRow *row,
+                    GtkListBoxRow *before,
+                    gpointer       data)
+{
+  GtkCombo *combo = data;
+  GtkWidget *ret = NULL;
+
+  if ((GtkWidget*)before == combo->back_to_list &&
+      !gtk_list_box_row_get_header (row))
+    {
+      ret = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+      gtk_list_box_row_set_header (row, ret);
+    }
+}
+
+static void
+search_changed (GtkSearchEntry *entry,
+                GtkCombo       *combo)
+{
+  expand_list (combo);
+  gtk_list_box_invalidate_filter (GTK_LIST_BOX (combo->list));
+}
+
+static void
+reset_popover (GtkWidget *widget,
+               GtkCombo  *combo)
+{
+  gtk_stack_set_visible_child_name (GTK_STACK (combo->stack), "list");
+  gtk_entry_set_text (GTK_ENTRY (combo->search_entry), "");
+  collapse_list (combo);
+  gtk_entry_set_text (GTK_ENTRY (combo->custom_entry), "");
+}
+
+typedef struct
+{
+  const gchar *id;
+  GtkWidget *row;
+} ForeachData;
+
+static void
+find_row (GtkWidget *row,
+          gpointer   data)
+{
+  ForeachData *d = data;
+  const gchar *id;
+
+  if (!GTK_IS_COMBO_ROW (row))
+    return;
+
+  id = gtk_combo_row_get_id (GTK_COMBO_ROW (row));
+
+  if (g_strcmp0 (id, d->id) == 0)
+    d->row = row;
+}
+
+static void
+add_to_list (GtkCombo    *combo,
+             const gchar *id,
+             const gchar *text,
+             const gchar *sort)
+{
+  GtkWidget *row;
+  ForeachData data;
+
+  data.id = id;
+  data.row = NULL;
+  gtk_container_foreach (GTK_CONTAINER (combo->list), find_row, &data);
+
+  if (data.row)
+    {
+      gtk_combo_row_set_sort (GTK_COMBO_ROW (data.row), sort);
+      gtk_combo_row_set_text (GTK_COMBO_ROW (data.row), text);
+      gtk_list_box_invalidate_sort (GTK_LIST_BOX (combo->list));
+      gtk_list_box_invalidate_filter (GTK_LIST_BOX (combo->list));
+    }
+  else
+    {
+      row = gtk_combo_row_new (id, sort, text);
+      gtk_list_box_insert (GTK_LIST_BOX (combo->list), row, -1);
+    }
+}
+
+static gboolean
+remove_from_list (GtkCombo    *combo,
+                  const gchar *id)
+{
+  ForeachData data;
+
+  data.id = id;
+  data.row = NULL;
+  gtk_container_foreach (GTK_CONTAINER (combo->list), find_row, &data);
+
+  if (data.row)
+    {
+      gtk_container_remove (GTK_CONTAINER (combo->list), data.row);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+update_check (GtkWidget *row,
+              gpointer   data)
+{
+  ForeachData *d = data;
+  const gchar *id;
+
+  if (!GTK_IS_COMBO_ROW (row))
+    return;
+
+  id = gtk_combo_row_get_id (GTK_COMBO_ROW (row));
+  if (g_strcmp0 (id, d->id) == 0)
+    {
+      g_object_set (row, "active", TRUE, NULL);
+      d->row = row;
+    }
+  else
+    {
+      g_object_set (row, "active", FALSE, NULL);
+    }
+}
+
+static void
+set_active (GtkCombo    *combo,
+            const gchar *id)
+{
+  const gchar *key;
+  const gchar *text;
+  ForeachData data;
+
+  data.id = id;
+  data.row = NULL;
+  gtk_container_foreach (GTK_CONTAINER (combo->list), update_check, &data);
+
+  if (!data.row)
+    {
+      key = NULL;
+      text = combo->placeholder;
+    }
+  else
+    {
+      key = gtk_combo_row_get_id (GTK_COMBO_ROW (data.row));
+      text = gtk_combo_row_get_text (GTK_COMBO_ROW (data.row));
+    }
+
+  combo->active = key;
+  gtk_label_set_text (GTK_LABEL (combo->active_label), text);
+
+  g_object_notify (G_OBJECT (combo), "active");
+}
+
+static void
+list_row_activated (GtkListBox    *list,
+                    GtkListBoxRow *row,
+                    GtkCombo      *combo)
+{
+  if ((GtkWidget*)row == combo->show_more)
+    {
+      expand_list (combo);
+      return;
+    }
+
+  if ((GtkWidget*)row == combo->add_custom)
+    {
+      gtk_stack_set_visible_child_name (GTK_STACK (combo->stack), "custom");
+      gtk_widget_grab_focus (combo->custom_entry);
+      return;
+    }
+
+  if (GTK_IS_COMBO_ROW (row))
+    set_active (combo, gtk_combo_row_get_id (GTK_COMBO_ROW (row)));
+
+  gtk_widget_hide (combo->popover);
+}
+
+static void
+custom_row_activated (GtkListBox    *list,
+                      GtkListBoxRow *row,
+                      GtkCombo      *combo)
+{
+  if ((GtkWidget*)row == combo->back_to_list)
+    {
+      gtk_stack_set_visible_child_name (GTK_STACK (combo->stack), "list");
+    }
+}
+
+static void
+custom_entry_done (GtkWidget *widget,
+                   GtkCombo  *combo)
+{
+  const gchar *text;
+
+  text = gtk_entry_get_text (GTK_ENTRY (combo->custom_entry));
+  if (text[0] != '\0')
+    {
+      gtk_combo_add_item (combo, text, text, text);
+      gtk_combo_set_active (combo, text);
+      gtk_entry_set_text (GTK_ENTRY (combo->custom_entry), "");
+      gtk_widget_hide (combo->popover);
+    }
+}
+
+static void
+custom_entry_changed (GObject    *entry,
+                      GParamSpec *pspec,
+                      GtkCombo   *combo)
+{
+  const gchar *text;
+
+  text = gtk_entry_get_text (GTK_ENTRY (combo->custom_entry));
+  gtk_widget_set_sensitive (combo->custom_button, text[0] != '\0');
+}
+
+static void
+update_scrollbar (GtkCombo *combo,
+                  gboolean  allow)
+{
+  GtkPolicyType policy;
+
+  g_object_get (combo->scrolled_window, "vscrollbar-policy", &policy, NULL);
+
+  if ((allow && policy == GTK_POLICY_AUTOMATIC) ||
+      (!allow && policy == GTK_POLICY_NEVER))
+    return;
+
+  if (allow)
+    {
+      gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (combo->scrolled_window),
+                                                  gtk_widget_get_allocated_height (combo->list));
+      g_object_set (combo->scrolled_window, "vscrollbar-policy", GTK_POLICY_AUTOMATIC, NULL);
+    }
+  else
+    {
+      gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (combo->scrolled_window),
+                                                  -1);
+      g_object_set (combo->scrolled_window, "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
+    }
+}
+
+static void
+show_few (GtkWidget *widget, gpointer data)
+{
+  gint *count = data;
+
+  if (!GTK_IS_COMBO_ROW (widget))
+    return;
+
+  if (*count < 6)
+    gtk_widget_show (widget);
+  else
+    gtk_widget_hide (widget);
+
+  (*count)++;
+}
+
+static void
+collapse_list (GtkCombo *combo)
+{
+  gint count;
+
+  count = 0;
+  gtk_container_foreach (GTK_CONTAINER (combo->list), show_few, &count);
+  if (count < 7)
+    {
+      gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), FALSE);
+      gtk_widget_hide (combo->show_more);
+    }
+  else
+    {
+      gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), TRUE);
+      gtk_widget_show (combo->show_more);
+    }
+
+  update_scrollbar (combo, FALSE);
+}
+
+static void
+show_all (GtkWidget *widget, gpointer data)
+{
+  if (!GTK_IS_COMBO_ROW (widget))
+    return;
+
+  gtk_widget_show (widget);
+}
+
+static void
+expand_list (GtkCombo *combo)
+{
+  if (gtk_widget_get_visible (combo->show_more))
+    {
+      gtk_container_foreach (GTK_CONTAINER (combo->list), show_all, NULL);
+      gtk_widget_hide (combo->show_more);
+      update_scrollbar (combo, TRUE);
+    }
+}
+
+static gboolean
+is_keynav_event (GdkEvent *event,
+                 guint     keyval)
+{
+  GdkModifierType state = 0;
+
+  gdk_event_get_state (event, &state);
+
+  if (keyval == GDK_KEY_Tab ||
+      keyval == GDK_KEY_KP_Tab ||
+      keyval == GDK_KEY_Up ||
+      keyval == GDK_KEY_KP_Up ||
+      keyval == GDK_KEY_Down ||
+      keyval == GDK_KEY_KP_Down ||
+      keyval == GDK_KEY_Left ||
+      keyval == GDK_KEY_KP_Left ||
+      keyval == GDK_KEY_Right ||
+      keyval == GDK_KEY_KP_Right ||
+      keyval == GDK_KEY_Home ||
+      keyval == GDK_KEY_KP_Home ||
+      keyval == GDK_KEY_End ||
+      keyval == GDK_KEY_KP_End ||
+      keyval == GDK_KEY_Page_Up ||
+      keyval == GDK_KEY_KP_Page_Up ||
+      keyval == GDK_KEY_Page_Down ||
+      keyval == GDK_KEY_KP_Page_Down ||
+      ((state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0))
+        return TRUE;
+
+ /* Other navigation events should get automatically
+  * ignored as they will not change the content of the entry
+  */
+
+  return FALSE;
+}
+
+static void
+preedit_changed_cb (GtkEntry  *entry,
+                    GtkWidget *popup,
+                    gboolean  *preedit_changed)
+{
+  *preedit_changed = TRUE;
+}
+
+static gboolean
+popover_key_press (GtkWidget *widget,
+                   GdkEvent  *event,
+                   GtkCombo  *combo)
+{
+  guint keyval;
+  gboolean handled;
+  gboolean preedit_changed;
+  guint preedit_change_id;
+  gboolean res;
+  gchar *old_text, *new_text;
+
+  if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (combo->stack)), "custom") == 0 ||
+      gtk_revealer_get_reveal_child (GTK_REVEALER (combo->search_revealer)) ||
+      !gdk_event_get_keyval (event, &keyval) ||
+      is_keynav_event (event, keyval) ||
+      keyval == GDK_KEY_space ||
+      keyval == GDK_KEY_Menu)
+    return GDK_EVENT_PROPAGATE;
+
+  if (!gtk_widget_get_realized (combo->search_entry))
+    gtk_widget_realize (combo->search_entry);
+
+  handled = GDK_EVENT_PROPAGATE;
+  preedit_changed = FALSE;
+  preedit_change_id = g_signal_connect (combo->search_entry, "preedit-changed",
+                                        G_CALLBACK (preedit_changed_cb), &preedit_changed);
+
+  old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (combo->search_entry)));
+  res = gtk_widget_event (combo->search_entry, event);
+  new_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (combo->search_entry)));
+
+  g_signal_handler_disconnect (combo->search_entry, preedit_change_id);
+
+  if ((res && g_strcmp0 (new_text, old_text) != 0) || preedit_changed)
+    {
+      handled = GDK_EVENT_STOP;
+      gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), TRUE);
+      gtk_entry_grab_focus_without_selecting (GTK_ENTRY (combo->search_entry));
+    }
+
+  g_free (old_text);
+  g_free (new_text);
+
+  return handled;
+}
+
+static gboolean
+button_key_press (GtkWidget *widget,
+                  GdkEvent  *event,
+                  GtkCombo  *combo)
+{
+  gtk_widget_show (combo->popover);
+  return popover_key_press (combo->popover, event, combo);
+}
+
+
+/***/
+
+GtkWidget *
+gtk_combo_new (void)
+{
+  return g_object_new (GTK_TYPE_COMBO, NULL);
+}
+
+const gchar *
+gtk_combo_get_active (GtkCombo *combo)
+{
+  g_return_val_if_fail (GTK_IS_COMBO (combo), NULL);
+
+  return combo->active;
+}
+
+void
+gtk_combo_set_active (GtkCombo    *combo,
+                      const gchar *id)
+{
+  g_return_if_fail (GTK_IS_COMBO (combo));
+
+  set_active (combo, id);
+}
+
+void
+gtk_combo_add_item (GtkCombo    *combo,
+                    const gchar *id,
+                    const gchar *text,
+                    const gchar *sort)
+{
+  g_return_if_fail (GTK_IS_COMBO (combo));
+
+  add_to_list (combo, id, text, sort);
+  collapse_list (combo);
+}
+
+void
+gtk_combo_remove_item (GtkCombo    *combo,
+                       const gchar *id)
+{
+  g_return_if_fail (GTK_IS_COMBO (combo));
+
+  if (!remove_from_list (combo, id))
+    return;
+
+  if (g_strcmp0 (id, combo->active) == 0)
+    set_active (combo, NULL);
+
+  collapse_list (combo);
+}
+
+void
+gtk_combo_set_placeholder (GtkCombo    *combo,
+                           const gchar *text)
+{
+  g_return_if_fail (GTK_IS_COMBO (combo));
+
+  g_free (combo->placeholder);
+  combo->placeholder = g_strdup (text);
+
+  if (combo->active == NULL)
+    gtk_label_set_text (GTK_LABEL (combo->active_label), combo->placeholder);
+
+  g_object_notify (G_OBJECT (combo), "placeholder");
+}
+
+const gchar *
+gtk_combo_get_placeholder (GtkCombo *combo)
+{
+  g_return_val_if_fail (GTK_IS_COMBO (combo), NULL);
+
+  return combo->placeholder;
+}
+
+void
+gtk_combo_set_allow_custom (GtkCombo *combo,
+                            gboolean  allow)
+{
+  g_return_if_fail (GTK_IS_COMBO (combo));
+
+  if (combo->allow_custom == allow)
+    return;
+
+  combo->allow_custom = allow;
+
+  gtk_widget_set_visible (combo->add_custom, allow);
+
+  g_object_notify (G_OBJECT (combo), "allow-custom");
+}
+
+gboolean
+gtk_combo_get_allow_custom (GtkCombo *combo)
+{
+  g_return_val_if_fail (GTK_IS_COMBO (combo), FALSE);
+
+  return combo->allow_custom;
+}
diff --git a/gtk/gtkcombo.h b/gtk/gtkcombo.h
new file mode 100644
index 0000000..071a62d
--- /dev/null
+++ b/gtk/gtkcombo.h
@@ -0,0 +1,77 @@
+/* GTK - The GIMP Toolkit
+ *
+ * Copyright (C) 2014 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/>.
+ */
+
+#ifndef __GTK_COMBO_H__
+#define __GTK_COMBO_H__
+
+#if defined(GTK_DISABLE_SINGLE_INCLUDES) && !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_COMBO                 (gtk_combo_get_type ())
+#define GTK_COMBO(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_COMBO, GtkCombo))
+#define GTK_COMBO_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_COMBO, GtkComboClass))
+#define GTK_IS_COMBO(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_COMBO))
+#define GTK_IS_COMBO_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_COMBO))
+#define GTK_COMBO_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_COMBO, GtkComboClass))
+
+typedef struct _GtkCombo             GtkCombo;
+typedef struct _GtkComboClass        GtkComboClass;
+
+GDK_AVAILABLE_IN_3_16
+GType         gtk_combo_get_type         (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_16
+GtkWidget *   gtk_combo_new              (void);
+
+GDK_AVAILABLE_IN_3_16
+const gchar * gtk_combo_get_active       (GtkCombo    *combo);
+
+GDK_AVAILABLE_IN_3_16
+void          gtk_combo_set_active       (GtkCombo    *combo,
+                                          const gchar *id);
+
+GDK_AVAILABLE_IN_3_16
+void          gtk_combo_add_item         (GtkCombo    *combo,
+                                          const gchar *id,
+                                          const gchar *text,
+                                          const gchar *sort);
+GDK_AVAILABLE_IN_3_16
+void          gtk_combo_remove_item      (GtkCombo    *combo,
+                                          const gchar *id);
+
+GDK_AVAILABLE_IN_3_16
+const gchar * gtk_combo_get_placeholder  (GtkCombo    *combo);
+
+GDK_AVAILABLE_IN_3_16
+void          gtk_combo_set_placeholder  (GtkCombo    *combo,
+                                          const gchar *text);
+
+GDK_AVAILABLE_IN_3_16
+gboolean      gtk_combo_get_allow_custom (GtkCombo    *combo);
+GDK_AVAILABLE_IN_3_16
+void          gtk_combo_set_allow_custom (GtkCombo    *combo,
+                                          gboolean     allow);
+
+G_END_DECLS
+
+#endif /* __GTK_COMBO_H__ */
diff --git a/gtk/ui/gtkcombo.ui b/gtk/ui/gtkcombo.ui
new file mode 100644
index 0000000..496c368
--- /dev/null
+++ b/gtk/ui/gtkcombo.ui
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="GtkCombo" parent="GtkBin">
+    <child>
+      <object class="GtkToggleButton" id="button">
+        <property name="visible">True</property>
+        <signal name="key-press-event" handler="button_key_press"/>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">horizontal</property>
+            <property name="spacing">6</property>
+            <child type="center">
+              <object class="GtkLabel" id="active_label">
+                <property name="visible">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="halign">end</property>
+                <property name="valign">center</property>
+                <property name="icon-name">pan-down-symbolic</property>
+                <property name="icon-size">1</property>
+              </object>
+              <packing>
+                <property name="pack-type">end</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkPopover" id="popover">
+    <property name="relative-to">button</property>
+    <property name="position">bottom</property>
+    <property name="modal">True</property>
+    <property name="visible" bind-source="button" bind-property="active" bind-flags="bidirectional"/>
+    <signal name="hide" handler="reset_popover"/>
+    <signal name="key-press-event" handler="popover_key_press"/>
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">True</property>
+        <property name="vhomogeneous">False</property>
+        <property name="transition-type">slide-left-right</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="margin">10</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkRevealer" id="search_revealer">
+                <property name="visible">True</property>
+                <property name="transition-type">slide-down</property>
+                <child>
+                  <object class="GtkSearchEntry" id="search_entry">
+                    <property name="visible">True</property>
+                    <property name="width-chars">15</property>
+                    <property name="max-width-chars">15</property>
+                    <signal name="search-changed" handler="search_changed"/>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkScrolledWindow" id="scrolled_window">
+                <property name="visible">True</property>
+                <property name="shadow-type">in</property>
+                <property name="hscrollbar-policy">never</property>
+                <property name="vscrollbar-policy">never</property>
+                <child>
+                  <object class="GtkListBox" id="list">
+                    <property name="visible">True</property>
+                    <property name="selection-mode">none</property>
+                    <property name="width-request">100</property>
+                    <signal name="row-activated" handler="list_row_activated"/>
+                    <child type="placeholder">
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="margin">6</property>
+                        <property name="label" translatable="yes">No match</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow" id="show_more">
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="margin">6</property>
+                            <property name="halign">center</property>
+                            <property name="valign">center</property>
+                            <property name="icon-name">view-more-symbolic</property>
+                            <property name="icon-size">1</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkListBoxRow" id="add_custom">
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="orientation">horizontal</property>
+                            <property name="margin">6</property>
+                            <property name="spacing">12</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="label" translatable="yes">Custom Entry</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="visible">True</property>
+                                <property name="halign">end</property>
+                                <property name="valign">center</property>
+                                <property name="icon-name">pan-end-symbolic</property>
+                                <property name="icon-size">1</property>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">list</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkFrame">
+            <property name="visible">True</property>
+            <property name="margin">10</property>
+            <child>
+              <object class="GtkListBox" id="custom">
+                <property name="visible">True</property>
+                <property name="selection-mode">none</property>
+                <property name="width-request">100</property>
+                <signal name="row-activated" handler="custom_row_activated"/>
+                <child>
+                  <object class="GtkListBoxRow" id="back_to_list">
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="orientation">horizontal</property>
+                        <property name="margin">6</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="visible">True</property>
+                            <property name="halign">start</property>
+                            <property name="valign">center</property>
+                            <property name="icon-name">pan-start-symbolic</property>
+                            <property name="icon-size">1</property>
+                          </object>
+                        </child>
+                        <child type="center">
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="label" translatable="yes">Custom Entry</property>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkListBoxRow">
+                    <property name="visible">True</property>
+                    <property name="activatable">False</property>
+                    <child>
+                      <object class="GtkEntry" id="custom_entry">
+                        <property name="visible">True</property>
+                        <property name="margin">6</property>
+                        <signal name="activate" handler="custom_entry_done"/>
+                        <signal name="notify::text" handler="custom_entry_changed"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkListBoxRow">
+                    <property name="visible">True</property>
+                    <property name="activatable">False</property>
+                    <child>
+                      <object class="GtkButton" id="custom_button">
+                        <property name="visible">True</property>
+                        <property name="sensitive">False</property>
+                        <property name="margin">6</property>
+                        <property name="label" translatable="yes">Done</property>
+                        <signal name="clicked" handler="custom_entry_done"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">custom</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <property name="ignore-hidden">False</property>
+    <widgets>
+      <widget name="search_entry"/>
+      <widget name="scrolled_window"/>
+    </widgets>
+  </object>
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="button"/>
+      <widget name="stack"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/gtk/ui/gtkcombo.ui.h b/gtk/ui/gtkcombo.ui.h
new file mode 100644
index 0000000..2c815b5
--- /dev/null
+++ b/gtk/ui/gtkcombo.ui.h
@@ -0,0 +1,4 @@
+N_("No match");
+N_("Custom Entry");
+N_("Custom Entry");
+N_("Done");
diff --git a/gtk/ui/gtkcomborow.ui b/gtk/ui/gtkcomborow.ui
new file mode 100644
index 0000000..f61f989
--- /dev/null
+++ b/gtk/ui/gtkcomborow.ui
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="GtkComboRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="orientation">horizontal</property>
+        <property name="margin">6</property>
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="label">
+            <property name="visible">True</property>
+            <property name="label" bind-source="GtkComboRow" bind-property="text"/>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage" id="check">
+            <property name="icon-name">object-select-symbolic</property>
+            <property name="icon-size">1</property>
+            <property name="visible" bind-source="GtkComboRow" bind-property="active"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/gtk/ui/gtkcomborow.ui.h b/gtk/ui/gtkcomborow.ui.h
new file mode 100644
index 0000000..e69de29
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 62aef23..7d1b6b8 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -340,6 +340,8 @@ gtk/ui/gtkapplication-quartz.ui.h
 gtk/ui/gtkassistant.ui.h
 gtk/ui/gtkcolorchooserdialog.ui.h
 gtk/ui/gtkcoloreditor.ui.h
+gtk/ui/gtkcombo.ui.h
+gtk/ui/gtkcomborow.ui.h
 gtk/ui/gtkdialog.ui.h
 gtk/ui/gtkfilechooserbutton.ui.h
 gtk/ui/gtkfilechooserdialog.ui.h


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]