[gtk+/wip/combo: 4/5] Support grouping in the new combo box



commit 8a91e0b987918dc867e0aa6e4932b4a80fce2812
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Dec 25 20:56:19 2014 -0500

    Support grouping in the new combo box

 docs/reference/gtk/gtk3-sections.txt |    1 +
 gtk/Makefile.am                      |    1 +
 gtk/gtkcombo.c                       |  713 ++++++++++++++++++++++++++--------
 gtk/gtkcombo.h                       |   10 +-
 gtk/ui/gtkcombo.ui                   |   63 +---
 gtk/ui/gtkcomborow.ui                |    2 +-
 gtk/ui/gtkcombotab.ui                |   42 ++
 po/POTFILES.in                       |    1 +
 8 files changed, 608 insertions(+), 225 deletions(-)
---
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index a2c896a..88d56f8 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -984,6 +984,7 @@ GtkCombo
 gtk_combo_new
 gtk_combo_add_item
 gtk_combo_remove_item
+gtk_combo_add_group
 gtk_combo_get_active
 gtk_combo_set_active
 gtk_combo_get_placeholder
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index d38643f..9ac2f34 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -1051,6 +1051,7 @@ templates =                               \
        ui/gtkcoloreditor.ui            \
        ui/gtkcombo.ui                  \
        ui/gtkcomborow.ui               \
+       ui/gtkcombotab.ui               \
        ui/gtkdialog.ui                 \
        ui/gtkfilechooserbutton.ui      \
        ui/gtkfilechooserwidget.ui      \
diff --git a/gtk/gtkcombo.c b/gtk/gtkcombo.c
index 5e7a298..20a0349 100644
--- a/gtk/gtkcombo.c
+++ b/gtk/gtkcombo.c
@@ -26,7 +26,9 @@
 #include "gtkbuildable.h"
 #include "gtkbuilderprivate.h"
 #include "gtkbutton.h"
+#include "gtkcontainer.h"
 #include "gtkentry.h"
+#include "gtkframe.h"
 #include "gtklabel.h"
 #include "gtklistbox.h"
 #include "gtkimage.h"
@@ -59,6 +61,10 @@
  * If you want to allow the user to enter custom values, use
  * gtk_combo_set_allow_custom().
  *
+ * Items can optionally be grouped, by specifying a group id as the last
+ * argument to gtk_combo_add_item(). Groups can have display text and sort
+ * keys that are different from the group id, by using gtk_combo_box_add_group().
+ *
  * # GtkCombo as GtkBuildable
  *
  * The GtkCombo implementation of the GtkBuildable interface supports
@@ -66,7 +72,8 @@
  * elements for each item. Each <item> element can specify the “id”
  * and "sort" corresponding to the appended text and also supports
  * the regular translation attributes “translatable”, “context” and
- * “comments”.
+ * “comments”. Groups can be specified similarly with <groups> and
+ * <group> elements, and associated with items with the “group” attribute.
  *
  * Here is a UI definition fragment specifying some GtkCombo items:
  * |[
@@ -74,8 +81,11 @@
  *   <items>
  *     <item translatable="yes" id="factory" sort="aaa">Factory</item>
  *     <item translatable="yes" id="home" sort="ccc">Home</item>
- *     <item translatable="yes" id="subway" sort="bbb">Subway</item>
+ *     <item translatable="yes" id="subway" sort="bbb" group="group1">Subway</item>
  *   </items>
+ *   <groups>
+ *     <group translatable="yes" id="group1">Subterranean</group>
+ *   </groups>
  * </object>
  * ]|
  */
@@ -92,19 +102,30 @@ typedef struct
   gchar *id;
   gchar *text;
   gchar *sort;
+  gchar *group;
+  gboolean inverted;
   gboolean active;
 
   GtkWidget *label;
   GtkWidget *check;
+  GtkWidget *box;
+  GtkWidget *arrow;
 } GtkComboRow;
 
 enum {
   ROW_PROP_ACTIVE = 1,
   ROW_PROP_ID,
   ROW_PROP_SORT,
-  ROW_PROP_TEXT
+  ROW_PROP_TEXT,
+  ROW_PROP_GROUP,
+  ROW_PROP_INVERTED
 };
 
+static void gtk_combo_row_set_group    (GtkComboRow *row,
+                                        const gchar *group);
+static void gtk_combo_row_set_inverted (GtkComboRow *row,
+                                        gboolean     inverted);
+
 typedef GtkListBoxRowClass GtkComboRowClass;
 
 G_DEFINE_TYPE (GtkComboRow, gtk_combo_row, GTK_TYPE_LIST_BOX_ROW)
@@ -123,6 +144,7 @@ gtk_combo_row_finalize (GObject *object)
   g_free (row->id);
   g_free (row->text);
   g_free (row->sort);
+  g_free (row->group);
 
   G_OBJECT_CLASS (gtk_combo_row_parent_class)->finalize (object);
 }
@@ -152,6 +174,12 @@ gtk_combo_row_set_property (GObject      *object,
       g_free (row->sort);
       row->sort = g_value_dup_string (value);
       break;
+    case ROW_PROP_GROUP:
+      gtk_combo_row_set_group (row, g_value_get_string (value));
+      break;
+    case ROW_PROP_INVERTED:
+      gtk_combo_row_set_inverted (row, g_value_get_boolean (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -179,6 +207,9 @@ gtk_combo_row_get_property (GObject    *object,
     case ROW_PROP_SORT:
       g_value_set_string (value, row->sort);
       break;
+    case ROW_PROP_GROUP:
+      g_value_set_string (value, row->group);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -214,23 +245,111 @@ gtk_combo_row_class_init (GtkComboRowClass *class)
                                    g_param_spec_string ("sort", P_("Sort"), P_("Sort"),
                                                         NULL,
                                                         GTK_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+                                   ROW_PROP_GROUP,
+                                   g_param_spec_string ("group", P_("Group"), P_("Group"),
+                                                        NULL,
+                                                        GTK_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+                                   ROW_PROP_INVERTED,
+                                   g_param_spec_boolean ("inverted", P_("Inverted"), P_("Inverted"),
+                                                         FALSE,
+                                                         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, box);
   gtk_widget_class_bind_template_child (widget_class, GtkComboRow, label);
   gtk_widget_class_bind_template_child (widget_class, GtkComboRow, check);
 }
 
+static void
+update_group (GtkComboRow *row)
+{
+  g_object_ref (row->label);
+  g_object_ref (row->check);
+
+  gtk_container_remove (GTK_CONTAINER (row->box), row->label);
+  gtk_container_remove (GTK_CONTAINER (row->box), row->check);
+
+  if (row->group)
+    {
+      if (!row->arrow)
+        {
+          row->arrow = gtk_image_new ();
+          gtk_widget_show (row->arrow);
+          gtk_box_pack_end (GTK_BOX (row->box), row->arrow, FALSE, FALSE, 0);
+        }
+
+      if (row->inverted)
+        {
+          gtk_box_set_center_widget (GTK_BOX (row->box), row->label);
+          gtk_container_child_set (GTK_CONTAINER (row->box), row->arrow, "pack-type", GTK_PACK_START, NULL);
+          gtk_image_set_from_icon_name (GTK_IMAGE (row->arrow), "pan-start-symbolic", GTK_ICON_SIZE_MENU);
+        }
+      else
+        {
+          gtk_box_pack_start (GTK_BOX (row->box), row->label, FALSE, FALSE, 0);
+          gtk_container_child_set (GTK_CONTAINER (row->box), row->arrow, "pack-type", GTK_PACK_END, NULL);
+          gtk_image_set_from_icon_name (GTK_IMAGE (row->arrow), "pan-end-symbolic", GTK_ICON_SIZE_MENU);
+        }
+    }
+  else
+    {
+      if (row->arrow)
+        {
+          gtk_container_remove (GTK_CONTAINER (row->box), row->arrow);
+          row->arrow = NULL;
+        }
+
+      gtk_box_pack_start (GTK_BOX (row->box), row->label, FALSE, FALSE, 0);
+    }
+
+  gtk_box_pack_start (GTK_BOX (row->box), row->check, FALSE, FALSE, 0);
+
+  g_object_unref (row->label);
+  g_object_unref (row->check);
+}
+
+static void
+gtk_combo_row_set_group (GtkComboRow *row,
+                         const gchar *group)
+{
+  g_free (row->group);
+  row->group = g_strdup (group);
+  update_group (row);
+}
+
+static void
+gtk_combo_row_set_inverted (GtkComboRow *row,
+                            gboolean     inverted)
+{
+  row->inverted = inverted;
+  update_group (row);
+}
+
+static GtkWidget *
+gtk_combo_row_new_item (const gchar *id,
+                        const gchar *text,
+                        const gchar *sort)
+{
+  return g_object_new (GTK_TYPE_COMBO_ROW, "id", id, "text", text, "sort", sort, NULL);
+}
+
 static GtkWidget *
-gtk_combo_row_new (const gchar *id,
-                   const gchar *sort,
-                   const gchar *text)
+gtk_combo_row_new_group (const gchar *group,
+                         const gchar *text,
+                         const gchar *sort)
+{
+  return g_object_new (GTK_TYPE_COMBO_ROW, "group", group, "text", text, "sort", sort, NULL);
+}
+
+static void
+gtk_combo_row_set_active (GtkComboRow *row,
+                          gboolean     active)
 {
-  return g_object_new (GTK_TYPE_COMBO_ROW,
-                       "id", id,
-                       "sort", sort,
-                       "text", text,
-                       NULL);
+  row->active = active;
+  g_object_notify (G_OBJECT (row), "active");
 }
 
 static void
@@ -239,7 +358,6 @@ gtk_combo_row_set_text (GtkComboRow *row,
 {
   g_free (row->text);
   row->text = g_strdup (text);
-
   g_object_notify (G_OBJECT (row), "text");
 }
 
@@ -249,7 +367,6 @@ gtk_combo_row_set_sort (GtkComboRow *row,
 {
   g_free (row->sort);
   row->sort = g_strdup (sort);
-
   g_object_notify (G_OBJECT (row), "sort");
 }
 
@@ -262,7 +379,7 @@ gtk_combo_row_get_id (GtkComboRow *row)
 static const gchar *
 gtk_combo_row_get_text (GtkComboRow *row)
 {
-  return row->text ? row->text : row->id;
+  return row->text;
 }
 
 static const gchar *
@@ -271,6 +388,11 @@ gtk_combo_row_get_sort (GtkComboRow *row)
   return row->sort ? row->sort : gtk_combo_row_get_text (row);
 }
 
+static const gchar *
+gtk_combo_row_get_group (GtkComboRow *row)
+{
+  return row->group;
+}
 
 /***/
 
@@ -280,9 +402,6 @@ static void     list_row_activated   (GtkListBox     *list,
                                       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,
@@ -296,8 +415,10 @@ static gboolean button_key_press     (GtkWidget      *widget,
                                       GtkCombo       *combo);
 static void     reset_popover        (GtkWidget      *popover,
                                       GtkCombo       *combo);
-static void     collapse_list        (GtkCombo       *combo);
-static void     expand_list          (GtkCombo       *combo);
+static void     collapse             (GtkCombo       *combo,
+                                      GtkWidget      *list);
+static void     expand               (GtkCombo       *combo,
+                                      GtkWidget      *list);
 static void     list_header_func     (GtkListBoxRow  *row,
                                       GtkListBoxRow  *before,
                                       gpointer        data);
@@ -309,6 +430,14 @@ static gint     list_sort_func       (GtkListBoxRow  *row1,
 static void     custom_header_func   (GtkListBoxRow  *row,
                                       GtkListBoxRow  *before,
                                       gpointer        data);
+static GtkWidget *group_get_list     (GtkCombo       *combo,
+                                      const gchar    *group);
+static GtkWidget *group_get_header   (GtkCombo       *combo,
+                                      const gchar    *group);
+static GtkWidget *group_get_item     (GtkCombo       *combo,
+                                      const gchar    *group);
+static void       ensure_group       (GtkCombo       *combo,
+                                      const gchar    *group);
 
 static void     gtk_combo_buildable_init (GtkBuildableIface *iface);
 
@@ -356,6 +485,8 @@ G_DEFINE_TYPE_WITH_CODE (GtkCombo, gtk_combo, GTK_TYPE_BIN,
 static void
 gtk_combo_init (GtkCombo *combo)
 {
+  g_type_ensure (GTK_TYPE_COMBO_ROW);
+
   gtk_widget_init_template (GTK_WIDGET (combo));
 
   gtk_list_box_set_header_func (GTK_LIST_BOX (combo->list), list_header_func, combo, NULL);
@@ -364,6 +495,8 @@ gtk_combo_init (GtkCombo *combo)
 
   gtk_list_box_set_header_func (GTK_LIST_BOX (combo->custom), custom_header_func, combo, NULL);
 
+  g_object_set_data (G_OBJECT (combo->list), "show-more-item", combo->show_more);
+
   reset_popover (combo->popover, combo);
 }
 
@@ -494,7 +627,6 @@ gtk_combo_class_init (GtkComboClass *class)
   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);
@@ -511,26 +643,27 @@ typedef struct {
   const gchar   *domain;
   gchar         *id;
   gchar         *sort;
-
+  gchar         *group;
   GString       *string;
-
   gchar         *context;
   guint          translatable : 1;
   guint          is_text      : 1;
-} ItemParserData;
+  guint          is_group     : 1;
+} ComboParserData;
 
 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;
+combo_start_element (GMarkupParseContext *context,
+                     const gchar         *element_name,
+                     const gchar        **names,
+                     const gchar        **values,
+                     gpointer             user_data,
+                     GError             **error)
+{
+  ComboParserData *data = (ComboParserData*)user_data;
   guint i;
 
-  if (strcmp (element_name, "item") == 0)
+  if ((strcmp (element_name, "item") == 0 && !data->is_group) ||
+      (strcmp (element_name, "group") == 0 && data->is_group))
     {
       data->is_text = TRUE;
       for (i = 0; names[i]; i++)
@@ -554,6 +687,8 @@ item_start_element (GMarkupParseContext *context,
             data->id = g_strdup (values[i]);
           else if (strcmp (names[i], "sort") == 0)
             data->sort = g_strdup (values[i]);
+          else if (strcmp (names[i], "group") == 0 && !data->is_group)
+            data->group = g_strdup (values[i]);
           else
             g_warning ("Unknown custom combo item attribute: %s", names[i]);
         }
@@ -561,25 +696,25 @@ item_start_element (GMarkupParseContext *context,
 }
 
 static void
-item_text (GMarkupParseContext *context,
-           const gchar         *text,
-           gsize                text_len,
-           gpointer             user_data,
-           GError             **error)
+combo_text (GMarkupParseContext *context,
+            const gchar         *text,
+            gsize                text_len,
+            gpointer             user_data,
+            GError             **error)
 {
-  ItemParserData *data = (ItemParserData*)user_data;
+  ComboParserData *data = (ComboParserData*)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)
+combo_end_element (GMarkupParseContext *context,
+                   const gchar         *element_name,
+                   gpointer             user_data,
+                   GError             **error)
 {
-  ItemParserData *data = (ItemParserData*)user_data;
+  ComboParserData *data = (ComboParserData*)user_data;
 
   if (data->string->len)
     {
@@ -593,7 +728,10 @@ item_end_element (GMarkupParseContext *context,
           g_string_assign (data->string, translated);
         }
 
-      gtk_combo_add_item (GTK_COMBO (data->object), data->id, data->string->str, data->sort);
+      if (data->is_group)
+        gtk_combo_add_group (GTK_COMBO (data->object), data->id, data->string->str, data->sort);
+      else
+        gtk_combo_add_item (GTK_COMBO (data->object), data->id, data->string->str, data->sort, data->group);
     }
 
   data->translatable = FALSE;
@@ -602,14 +740,18 @@ item_end_element (GMarkupParseContext *context,
   data->context = NULL;
   g_free (data->id);
   data->id = NULL;
+  g_free (data->sort);
+  data->sort = NULL;
+  g_free (data->group);
+  data->group = NULL;
   data->is_text = FALSE;
 }
 
-static const GMarkupParser item_parser =
+static const GMarkupParser combo_parser =
 {
-  item_start_element,
-  item_end_element,
-  item_text
+  combo_start_element,
+  combo_end_element,
+  combo_text
 };
 
 static gboolean
@@ -624,16 +766,18 @@ gtk_combo_buildable_custom_tag_start (GtkBuildable  *buildable,
                                                 tagname, parser, data))
     return TRUE;
 
-  if (strcmp (tagname, "items") == 0)
+  if (strcmp (tagname, "items") == 0 ||
+      strcmp (tagname, "groups") == 0)
     {
-      ItemParserData *parser_data;
+      ComboParserData *parser_data;
 
-      parser_data = g_slice_new0 (ItemParserData);
+      parser_data = g_slice_new0 (ComboParserData);
       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;
+      parser_data->is_group = tagname[0] == 'g';
+      *parser = combo_parser;
       *data = parser_data;
 
       return TRUE;
@@ -649,19 +793,20 @@ gtk_combo_buildable_custom_finished (GtkBuildable *buildable,
                                      const gchar  *tagname,
                                      gpointer      user_data)
 {
-  ItemParserData *data;
+  ComboParserData *data;
 
   buildable_parent_iface->custom_finished (buildable, builder, child,
                                            tagname, user_data);
 
-  if (strcmp (tagname, "items") == 0)
+  if (strcmp (tagname, "items") == 0 ||
+      strcmp (tagname, "groups") == 0)
     {
-      data = (ItemParserData*)user_data;
+      data = (ComboParserData*)user_data;
 
       g_object_unref (data->object);
       g_object_unref (data->builder);
       g_string_free (data->string, TRUE);
-      g_slice_free (ItemParserData, data);
+      g_slice_free (ComboParserData, data);
     }
 }
 
@@ -702,9 +847,6 @@ list_filter_func (GtkListBoxRow *row,
   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;
@@ -712,6 +854,9 @@ list_filter_func (GtkListBoxRow *row,
   if (!GTK_IS_COMBO_ROW (row))
     return TRUE;
 
+  if (gtk_combo_row_get_group (GTK_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;
@@ -721,37 +866,54 @@ list_filter_func (GtkListBoxRow *row,
   return ret;
 }
 
+static GtkWidget *
+list_get_show_more_item (GtkWidget *list)
+{
+  return (GtkWidget*)g_object_get_data (G_OBJECT (list), "show-more-item");
+}
+
 static gint
 list_sort_func (GtkListBoxRow *row1,
                 GtkListBoxRow *row2,
                 gpointer       data)
 {
-  GtkCombo *combo = data;
+  GtkWidget *show_more;
+  const gchar *sort1;
+  const gchar *sort2;
 
   if (row1 == row2)
     return 0;
 
-  if (GTK_IS_COMBO_ROW (row1) && GTK_IS_COMBO_ROW (row2))
+  sort1 = NULL;
+  sort2 = NULL;
+
+  if (GTK_IS_COMBO_ROW (row1))
     {
-      const gchar *sort1;
-      const gchar *sort2;
+      if (g_strcmp0 (gtk_combo_row_get_group (GTK_COMBO_ROW (row1)), "list") == 0 ||
+          g_strcmp0 (gtk_combo_row_get_group (GTK_COMBO_ROW (row1)), "custom") == 0)
+        return -1;
 
       sort1 = gtk_combo_row_get_sort (GTK_COMBO_ROW (row1));
-      sort2 = gtk_combo_row_get_sort (GTK_COMBO_ROW (row2));
+    }
+
+  if (GTK_IS_COMBO_ROW (row2))
+    {
+      if (g_strcmp0 (gtk_combo_row_get_group (GTK_COMBO_ROW (row2)), "list") == 0 ||
+          g_strcmp0 (gtk_combo_row_get_group (GTK_COMBO_ROW (row2)), "custom") == 0)
+        return 1;
 
-      return g_strcmp0 (sort1, sort2);
+      sort2 = gtk_combo_row_get_sort (GTK_COMBO_ROW (row2));
     }
 
-  if ((GtkWidget*)row1 == combo->add_custom)
-    return -1;
+  if (sort1 && sort2)
+    return g_strcmp0 (sort1, sort2);
 
-  if ((GtkWidget*)row1 == combo->show_more)
-    return 1;
+  show_more = list_get_show_more_item (gtk_widget_get_parent (GTK_WIDGET (row1)));
 
-  if ((GtkWidget*)row2 == combo->add_custom)
+  if ((GtkWidget*)row1 == show_more)
     return 1;
 
-  if ((GtkWidget*)row2 == combo->show_more)
+  if ((GtkWidget*)row2 == show_more)
     return -1;
 
   return 0;
@@ -777,7 +939,7 @@ static void
 search_changed (GtkSearchEntry *entry,
                 GtkCombo       *combo)
 {
-  expand_list (combo);
+  expand (combo, combo->list);
   gtk_list_box_invalidate_filter (GTK_LIST_BOX (combo->list));
 }
 
@@ -787,19 +949,22 @@ reset_popover (GtkWidget *widget,
 {
   gtk_stack_set_visible_child_name (GTK_STACK (combo->stack), "list");
   gtk_entry_set_text (GTK_ENTRY (combo->search_entry), "");
-  collapse_list (combo);
+  gtk_widget_hide (combo->search_revealer);
+  collapse (combo, combo->list);
   gtk_entry_set_text (GTK_ENTRY (combo->custom_entry), "");
 }
 
 typedef struct
 {
+  GtkCombo *combo;
   const gchar *id;
   GtkWidget *row;
+  GtkWidget *group_row;
 } ForeachData;
 
 static void
-find_row (GtkWidget *row,
-          gpointer   data)
+find_item (GtkWidget *row,
+           gpointer   data)
 {
   ForeachData *d = data;
   const gchar *id;
@@ -807,6 +972,9 @@ find_row (GtkWidget *row,
   if (!GTK_IS_COMBO_ROW (row))
     return;
 
+  if (gtk_combo_row_get_group (GTK_COMBO_ROW (row)))
+    return;
+
   id = gtk_combo_row_get_id (GTK_COMBO_ROW (row));
 
   if (g_strcmp0 (id, d->id) == 0)
@@ -814,32 +982,80 @@ find_row (GtkWidget *row,
 }
 
 static void
-add_to_list (GtkCombo    *combo,
+find_item_and_group (GtkWidget *row,
+                     gpointer   data)
+{
+  ForeachData *d = data;
+  const gchar *id;
+  const gchar *group;
+
+  if (d->row)
+    return;
+
+  if (!GTK_IS_COMBO_ROW (row))
+    return;
+
+  group = gtk_combo_row_get_group (GTK_COMBO_ROW (row));
+  if (group &&
+      g_strcmp0 (group, "list") != 0 &&
+      g_strcmp0 (group, "custom") != 0)
+    {
+      GtkWidget *list;
+
+      list = group_get_list (d->combo, group);
+      gtk_container_foreach (GTK_CONTAINER (list), find_item, d);
+
+      if (d->row)
+        d->group_row = row;
+    }
+
+  if (d->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 (GtkWidget   *list,
              const gchar *id,
              const gchar *text,
              const gchar *sort)
 {
-  GtkWidget *row;
   ForeachData data;
 
+  if (text == NULL)
+    text = id;
+
   data.id = id;
   data.row = NULL;
-  gtk_container_foreach (GTK_CONTAINER (combo->list), find_row, &data);
+  gtk_container_foreach (GTK_CONTAINER (list), find_item, &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));
+      gtk_combo_row_set_sort (GTK_COMBO_ROW (data.row), sort);
+      gtk_list_box_invalidate_sort (GTK_LIST_BOX (list));
+      gtk_list_box_invalidate_filter (GTK_LIST_BOX (list));
     }
   else
     {
-      row = gtk_combo_row_new (id, sort, text);
-      gtk_list_box_insert (GTK_LIST_BOX (combo->list), row, -1);
+      gtk_list_box_insert (GTK_LIST_BOX (list), gtk_combo_row_new_item (id, text, sort), -1);
     }
 }
 
+static void
+count_items (GtkWidget *widget,
+             gpointer   data)
+{
+  gint *count = data;
+
+  if (GTK_IS_COMBO_ROW (widget) &&
+      gtk_combo_row_get_id (GTK_COMBO_ROW (widget)))
+    (*count)++;
+}
+
 static gboolean
 remove_from_list (GtkCombo    *combo,
                   const gchar *id)
@@ -848,64 +1064,82 @@ remove_from_list (GtkCombo    *combo,
 
   data.id = id;
   data.row = NULL;
-  gtk_container_foreach (GTK_CONTAINER (combo->list), find_row, &data);
+  data.group_row = NULL;
+  gtk_container_foreach (GTK_CONTAINER (combo->list), find_item_and_group, &data);
 
   if (data.row)
     {
-      gtk_container_remove (GTK_CONTAINER (combo->list), data.row);
-      return TRUE;
-    }
+      GtkWidget *list;
+      GtkWidget *tab;
 
-  return FALSE;
-}
+      list = gtk_widget_get_parent (data.row);
+      gtk_container_remove (GTK_CONTAINER (list), data.row);
 
-static void
-update_check (GtkWidget *row,
-              gpointer   data)
-{
-  ForeachData *d = data;
-  const gchar *id;
-
-  if (!GTK_IS_COMBO_ROW (row))
-    return;
+      if (data.group_row)
+        {
+          gint count = 0;
+          gtk_container_foreach (GTK_CONTAINER (list), count_items, &count);
+          if (count == 0)
+            {
+              tab = gtk_widget_get_ancestor (list, GTK_TYPE_FRAME);
+              gtk_container_remove (GTK_CONTAINER (combo->stack), tab);
+              gtk_container_remove (GTK_CONTAINER (combo->list), data.group_row);
+            }
+          else
+            collapse (combo, list);
+        }
+      else
+        collapse (combo, list);
 
-  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);
+      return TRUE;
     }
+
+  return FALSE;
 }
 
 static void
 set_active (GtkCombo    *combo,
             const gchar *id)
 {
-  const gchar *key;
-  const gchar *text;
   ForeachData data;
 
+  data.combo = combo;
+  data.id = combo->active;
+  data.row = NULL;
+  data.group_row = NULL;
+  if (combo->active)
+    {
+      gtk_container_foreach (GTK_CONTAINER (combo->list), find_item_and_group, &data);
+      if (data.row)
+        gtk_combo_row_set_active (GTK_COMBO_ROW (data.row), FALSE);
+      if (data.group_row)
+        gtk_combo_row_set_active (GTK_COMBO_ROW (data.group_row), FALSE);
+    }
+
   data.id = id;
   data.row = NULL;
-  gtk_container_foreach (GTK_CONTAINER (combo->list), update_check, &data);
+  data.group_row = NULL;
+  if (id)
+    {
+      gtk_container_foreach (GTK_CONTAINER (combo->list), find_item_and_group, &data);
+      if (data.row)
+        gtk_combo_row_set_active (GTK_COMBO_ROW (data.row), TRUE);
+      if (data.group_row)
+        gtk_combo_row_set_active (GTK_COMBO_ROW (data.group_row), TRUE);
+    }
 
-  if (!data.row)
+  if (data.row)
     {
-      key = NULL;
-      text = combo->placeholder;
+      combo->active = gtk_combo_row_get_id (GTK_COMBO_ROW (data.row));
+      gtk_label_set_text (GTK_LABEL (combo->active_label),
+                          gtk_combo_row_get_text (GTK_COMBO_ROW (data.row)));
     }
   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 = NULL;
+      gtk_label_set_text (GTK_LABEL (combo->active_label), combo->placeholder);
     }
 
-  combo->active = key;
-  gtk_label_set_text (GTK_LABEL (combo->active_label), text);
 
   g_object_notify (G_OBJECT (combo), "active");
 }
@@ -915,9 +1149,9 @@ list_row_activated (GtkListBox    *list,
                     GtkListBoxRow *row,
                     GtkCombo      *combo)
 {
-  if ((GtkWidget*)row == combo->show_more)
+  if ((GtkWidget*)row == list_get_show_more_item (GTK_WIDGET (list)))
     {
-      expand_list (combo);
+      expand (combo, GTK_WIDGET (list));
       return;
     }
 
@@ -929,20 +1163,22 @@ list_row_activated (GtkListBox    *list,
     }
 
   if (GTK_IS_COMBO_ROW (row))
-    set_active (combo, gtk_combo_row_get_id (GTK_COMBO_ROW (row)));
+    {
+      const gchar *group;
 
-  gtk_widget_hide (combo->popover);
-}
+      group = gtk_combo_row_get_group (GTK_COMBO_ROW (row));
+      if (group)
+        {
+          if (g_strcmp0 (group, "list") != 0)
+            collapse (combo, group_get_list (combo, group));
+          gtk_stack_set_visible_child_name (GTK_STACK (combo->stack), group);
+          return;
+        }
 
-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");
+      set_active (combo, gtk_combo_row_get_id (GTK_COMBO_ROW (row)));
     }
+
+  gtk_widget_hide (combo->popover);
 }
 
 static void
@@ -954,7 +1190,7 @@ custom_entry_done (GtkWidget *widget,
   text = gtk_entry_get_text (GTK_ENTRY (combo->custom_entry));
   if (text[0] != '\0')
     {
-      gtk_combo_add_item (combo, text, text, text);
+      gtk_combo_add_item (combo, text, NULL, NULL, NULL);
       gtk_combo_set_active (combo, text);
       gtk_entry_set_text (GTK_ENTRY (combo->custom_entry), "");
       gtk_widget_hide (combo->popover);
@@ -973,12 +1209,14 @@ custom_entry_changed (GObject    *entry,
 }
 
 static void
-update_scrollbar (GtkCombo *combo,
-                  gboolean  allow)
+update_scrolling (GtkWidget *list,
+                  gboolean   allow)
 {
   GtkPolicyType policy;
+  GtkWidget *sw;
 
-  g_object_get (combo->scrolled_window, "vscrollbar-policy", &policy, NULL);
+  sw = gtk_widget_get_ancestor (list, GTK_TYPE_SCROLLED_WINDOW);
+  g_object_get (sw, "vscrollbar-policy", &policy, NULL);
 
   if ((allow && policy == GTK_POLICY_AUTOMATIC) ||
       (!allow && policy == GTK_POLICY_NEVER))
@@ -986,15 +1224,14 @@ update_scrollbar (GtkCombo *combo,
 
   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);
+      gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (sw),
+                                                  gtk_widget_get_allocated_height (list));
+      g_object_set (sw, "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);
+      gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (sw), -1);
+      g_object_set (sw, "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
     }
 }
 
@@ -1003,7 +1240,8 @@ show_few (GtkWidget *widget, gpointer data)
 {
   gint *count = data;
 
-  if (!GTK_IS_COMBO_ROW (widget))
+  if (!GTK_IS_COMBO_ROW (widget) ||
+      gtk_combo_row_get_group (GTK_COMBO_ROW (widget)) != NULL)
     return;
 
   if (*count < 6)
@@ -1015,43 +1253,56 @@ show_few (GtkWidget *widget, gpointer data)
 }
 
 static void
-collapse_list (GtkCombo *combo)
+collapse (GtkCombo  *combo,
+          GtkWidget *list)
 {
   gint count;
+  GtkWidget *show_more;
 
+  show_more = list_get_show_more_item (list);
   count = 0;
-  gtk_container_foreach (GTK_CONTAINER (combo->list), show_few, &count);
+  gtk_container_foreach (GTK_CONTAINER (list), show_few, &count);
   if (count < 7)
     {
-      gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), FALSE);
-      gtk_widget_hide (combo->show_more);
+      if (list == combo->list)
+        gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), FALSE);
+      gtk_widget_hide (show_more);
     }
   else
     {
-      gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), TRUE);
-      gtk_widget_show (combo->show_more);
+      if (list == combo->list)
+        {
+          gtk_widget_show (combo->search_revealer);
+          gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), TRUE);
+        }
+      gtk_widget_show (show_more);
     }
 
-  update_scrollbar (combo, FALSE);
+  update_scrolling (list, FALSE);
 }
 
 static void
 show_all (GtkWidget *widget, gpointer data)
 {
-  if (!GTK_IS_COMBO_ROW (widget))
+  if (!GTK_IS_COMBO_ROW (widget) ||
+      gtk_combo_row_get_group (GTK_COMBO_ROW (widget)) != NULL)
     return;
 
   gtk_widget_show (widget);
 }
 
 static void
-expand_list (GtkCombo *combo)
+expand (GtkCombo  *combo,
+        GtkWidget *list)
 {
-  if (gtk_widget_get_visible (combo->show_more))
+  GtkWidget *show_more;
+
+  show_more = list_get_show_more_item (list);
+  if (gtk_widget_get_visible (show_more))
     {
-      gtk_container_foreach (GTK_CONTAINER (combo->list), show_all, NULL);
-      gtk_widget_hide (combo->show_more);
-      update_scrollbar (combo, TRUE);
+      gtk_container_foreach (GTK_CONTAINER (list), show_all, NULL);
+      gtk_widget_hide (show_more);
+      update_scrolling (list, TRUE);
     }
 }
 
@@ -1111,8 +1362,7 @@ popover_key_press (GtkWidget *widget,
   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)) ||
+  if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (combo->stack)), "list") != 0 ||
       !gdk_event_get_keyval (event, &keyval) ||
       is_keynav_event (event, keyval) ||
       keyval == GDK_KEY_space ||
@@ -1136,6 +1386,7 @@ popover_key_press (GtkWidget *widget,
   if ((res && g_strcmp0 (new_text, old_text) != 0) || preedit_changed)
     {
       handled = GDK_EVENT_STOP;
+      gtk_widget_show (combo->search_revealer);
       gtk_revealer_set_reveal_child (GTK_REVEALER (combo->search_revealer), TRUE);
       gtk_entry_grab_focus_without_selecting (GTK_ENTRY (combo->search_entry));
     }
@@ -1151,8 +1402,89 @@ button_key_press (GtkWidget *widget,
                   GdkEvent  *event,
                   GtkCombo  *combo)
 {
-  gtk_widget_show (combo->popover);
-  return popover_key_press (combo->popover, event, combo);
+  gboolean handled;
+
+  handled = popover_key_press (combo->popover, event, combo);
+
+  if (handled == GDK_EVENT_STOP)
+    {
+      gtk_widget_show (combo->popover);
+      gtk_editable_select_region (GTK_EDITABLE (combo->search_entry), -1, -1);
+    }
+
+  return handled;
+}
+
+static GtkWidget *
+group_get_list (GtkCombo    *combo,
+                const gchar *group)
+{
+  GtkWidget *tab;
+
+  tab = gtk_stack_get_child_by_name (GTK_STACK (combo->stack), group);
+  g_return_val_if_fail (tab != NULL, NULL);
+  return (GtkWidget*)g_object_get_data (G_OBJECT (tab), "list");
+}
+
+static GtkWidget *
+group_get_header (GtkCombo    *combo,
+                  const gchar *group)
+{
+  GtkWidget *tab;
+
+  tab = gtk_stack_get_child_by_name (GTK_STACK (combo->stack), group);
+  g_return_val_if_fail (tab != NULL, NULL);
+  return (GtkWidget*)g_object_get_data (G_OBJECT (tab), "header");
+}
+
+static GtkWidget *
+group_get_item (GtkCombo    *combo,
+                const gchar *group)
+{
+  GtkWidget *tab;
+
+  tab = gtk_stack_get_child_by_name (GTK_STACK (combo->stack), group);
+  g_return_val_if_fail (tab != NULL, NULL);
+  return (GtkWidget*)g_object_get_data (G_OBJECT (tab), "item");
+}
+
+static void
+ensure_group (GtkCombo    *combo,
+              const gchar *group)
+{
+  GtkWidget *tab;
+
+  tab = gtk_stack_get_child_by_name (GTK_STACK (combo->stack), group);
+  if (tab == NULL)
+    {
+      GtkBuilder *builder;
+      GtkWidget *list;
+      GtkWidget *item;
+      GtkWidget *header;
+
+      builder = gtk_builder_new_from_resource ("/org/gtk/libgtk/ui/gtkcombotab.ui");
+      tab = (GtkWidget*)gtk_builder_get_object (builder, "tab");
+      list = (GtkWidget*)gtk_builder_get_object (builder, "list");
+      header = (GtkWidget*)gtk_builder_get_object (builder, "header");
+      item = (GtkWidget*)gtk_builder_get_object (builder, "show_more");
+      gtk_stack_add_named (GTK_STACK (combo->stack), tab, group);
+      g_object_unref (builder);
+
+      g_object_set_data (G_OBJECT (tab), "list", list);
+      g_object_set_data (G_OBJECT (tab), "header", header);
+      g_object_set_data (G_OBJECT (list), "show-more-item", item);
+
+      g_signal_connect (list, "row-activated", G_CALLBACK (list_row_activated), combo);
+      gtk_list_box_set_sort_func (GTK_LIST_BOX (list), list_sort_func, combo, NULL);
+      gtk_list_box_set_header_func (GTK_LIST_BOX (list), list_header_func, combo, NULL);
+
+      gtk_combo_row_set_text (GTK_COMBO_ROW (header), group);
+
+      item = gtk_combo_row_new_group (group, group, NULL);
+      gtk_list_box_insert (GTK_LIST_BOX (combo->list), item, -1);
+
+      g_object_set_data (G_OBJECT (tab), "item", item);
+    }
 }
 
 
@@ -1214,19 +1546,20 @@ gtk_combo_set_active (GtkCombo    *combo,
 }
 
 /**
- * gtk_combo_add:
+ * gtk_combo_add_item:
  * @combo: a #GtkCombo
  * @id: the ID for the item to add
- * @sort: (allow-none): a sort key for the item
  * @text: (allow-none): the text to display for the item
+ * @sort: (allow-none): a sort key for the item
+ * @group: (allow-none): the group for the item
  *
  * Adds an item to the combo.
  *
- * If an item with this ID already exists, its sort key
- * and display text will be updated with the new values.
+ * If an item with this ID already exists, its display text
+ * and sort key will be updated with the new values.
  *
- * If @sort is %NULL, the item will be sorted according to @text.
  * If @text is %NULL, the @id will be used to display the item.
+ * If @sort is %NULL, the item will be sorted according to @text.
  *
  * Since: 3.16
  */
@@ -1234,16 +1567,26 @@ void
 gtk_combo_add_item (GtkCombo    *combo,
                     const gchar *id,
                     const gchar *text,
-                    const gchar *sort)
+                    const gchar *sort,
+                    const gchar *group)
 {
+  GtkWidget *list;
+
   g_return_if_fail (GTK_IS_COMBO (combo));
 
-  add_to_list (combo, id, text, sort);
-  collapse_list (combo);
+  if (group)
+    {
+      ensure_group (combo, group);
+      list = group_get_list (combo, group);
+    }
+  else
+    list = combo->list;
+  add_to_list (list, id, text, sort);
+  collapse (combo, list);
 }
 
 /**
- * gtk_combo_remove:
+ * gtk_combo_remove_item:
  * @combo: a #GtkCombo
  * @id: the ID of the item to remove
  *
@@ -1265,8 +1608,6 @@ gtk_combo_remove_item (GtkCombo    *combo,
 
   if (g_strcmp0 (id, combo->active) == 0)
     set_active (combo, NULL);
-
-  collapse_list (combo);
 }
 
 /**
@@ -1357,3 +1698,37 @@ gtk_combo_get_allow_custom (GtkCombo *combo)
 
   return combo->allow_custom;
 }
+
+/**
+ * gtk_combo_add_group:
+ * @combo: a #GtkCombo
+ * @group: a group ID
+ * @text: (allow-none): An optional display text for the group
+ * @sort: (allow-none): An optional sort key for the group
+ *
+ * Associates a display text and sort key with a group of items.
+ *
+ * Since: 3.16
+ */
+void
+gtk_combo_add_group (GtkCombo    *combo,
+                     const gchar *group,
+                     const gchar *text,
+                     const gchar *sort)
+{
+  GtkWidget *header, *item;
+
+  g_return_if_fail (GTK_IS_COMBO (combo));
+
+  ensure_group (combo, group);
+
+  header = group_get_header (combo, group);
+  item = group_get_item (combo, group);
+
+  gtk_combo_row_set_text (GTK_COMBO_ROW (header), text);
+  gtk_combo_row_set_text (GTK_COMBO_ROW (item), text);
+  gtk_combo_row_set_sort (GTK_COMBO_ROW (item), sort);
+
+  gtk_list_box_invalidate_filter (GTK_LIST_BOX (combo->list));
+  gtk_list_box_invalidate_sort (GTK_LIST_BOX (combo->list));
+}
diff --git a/gtk/gtkcombo.h b/gtk/gtkcombo.h
index 071a62d..aaa9a85 100644
--- a/gtk/gtkcombo.h
+++ b/gtk/gtkcombo.h
@@ -54,7 +54,9 @@ GDK_AVAILABLE_IN_3_16
 void          gtk_combo_add_item         (GtkCombo    *combo,
                                           const gchar *id,
                                           const gchar *text,
-                                          const gchar *sort);
+                                          const gchar *sort,
+                                          const gchar *group);
+
 GDK_AVAILABLE_IN_3_16
 void          gtk_combo_remove_item      (GtkCombo    *combo,
                                           const gchar *id);
@@ -72,6 +74,12 @@ GDK_AVAILABLE_IN_3_16
 void          gtk_combo_set_allow_custom (GtkCombo    *combo,
                                           gboolean     allow);
 
+GDK_AVAILABLE_IN_3_16
+void          gtk_combo_add_group        (GtkCombo    *combo,
+                                          const gchar *group,
+                                          const gchar *text,
+                                          const gchar *sort);
+
 G_END_DECLS
 
 #endif /* __GTK_COMBO_H__ */
diff --git a/gtk/ui/gtkcombo.ui b/gtk/ui/gtkcombo.ui
index 496c368..8f6a919 100644
--- a/gtk/ui/gtkcombo.ui
+++ b/gtk/ui/gtkcombo.ui
@@ -53,7 +53,6 @@
             <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">
@@ -99,33 +98,10 @@
                       </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 class="GtkComboRow" id="add_custom">
+                        <property name="visible">False</property>
+                        <property name="group">custom</property>
+                        <property name="text" translatable="yes">Custom Entry</property>
                       </object>
                     </child>
                   </object>
@@ -146,33 +122,12 @@
                 <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"/>
+                <signal name="row-activated" handler="list_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 class="GtkComboRow" id="back_to_list">
+                    <property name="group">list</property>
+                    <property name="text" translatable="yes">Custom Entry</property>
+                    <property name="inverted">True</property>
                   </object>
                 </child>
                 <child>
diff --git a/gtk/ui/gtkcomborow.ui b/gtk/ui/gtkcomborow.ui
index f61f989..d32de0e 100644
--- a/gtk/ui/gtkcomborow.ui
+++ b/gtk/ui/gtkcomborow.ui
@@ -4,7 +4,7 @@
   <template class="GtkComboRow" parent="GtkListBoxRow">
     <property name="visible">True</property>
     <child>
-      <object class="GtkBox">
+      <object class="GtkBox" id="box">
         <property name="visible">True</property>
         <property name="orientation">horizontal</property>
         <property name="margin">6</property>
diff --git a/gtk/ui/gtkcombotab.ui b/gtk/ui/gtkcombotab.ui
new file mode 100644
index 0000000..a3d0755
--- /dev/null
+++ b/gtk/ui/gtkcombotab.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+  <!-- interface-requires gtk+ 3.10 -->
+  <object class="GtkFrame" id="tab">
+    <property name="visible">True</property>
+    <property name="margin">10</property>
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="visible">True</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>
+            <child>
+              <object class="GtkComboRow" id="header">
+                <property name="group">list</property>
+                <property name="inverted">True</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkListBoxRow" id="show_more">
+                <property name="visible">True</property>
+                <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>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7d1b6b8..bd71ff4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -342,6 +342,7 @@ gtk/ui/gtkcolorchooserdialog.ui.h
 gtk/ui/gtkcoloreditor.ui.h
 gtk/ui/gtkcombo.ui.h
 gtk/ui/gtkcomborow.ui.h
+gtk/ui/gtkcombotab.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]