[gnome-control-center/wip/region-panel: 41/42] region: New 'Add Input Source' dialog design



commit 87424f4a7d12dab7943532503ec78fd9e68d467a
Author: Rui Matos <tiagomatos gmail com>
Date:   Wed Feb 6 19:41:01 2013 +0100

    region: New 'Add Input Source' dialog design

 panels/region/cc-input-chooser.c | 1221 +++++++++++++++++++++++++++++++-------
 panels/region/cc-input-chooser.h |    2 +
 panels/region/cc-region-panel.c  |   24 +-
 panels/region/input-chooser.ui   |  160 ++----
 4 files changed, 1086 insertions(+), 321 deletions(-)
---
diff --git a/panels/region/cc-input-chooser.c b/panels/region/cc-input-chooser.c
index b250cc3..7a88436 100644
--- a/panels/region/cc-input-chooser.c
+++ b/panels/region/cc-input-chooser.c
@@ -19,8 +19,16 @@
 
 #define _GNU_SOURCE
 #include <config.h>
+#include <locale.h>
 #include <glib/gi18n.h>
 
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnome-desktop/gnome-languages.h>
+
+#include "egg-list-box/egg-list-box.h"
+
+#include "cc-common-language.h"
+#include "cc-util.h"
 #include "cc-input-chooser.h"
 
 #ifdef HAVE_IBUS
@@ -31,192 +39,997 @@
 #define INPUT_SOURCE_TYPE_XKB "xkb"
 #define INPUT_SOURCE_TYPE_IBUS "ibus"
 
+#define ARROW_NEXT "go-next-symbolic"
+#define ARROW_PREV "go-previous-symbolic"
+
+#define MAIN_WINDOW_WIDTH_RATIO 0.60
+
+typedef enum {
+  ROW_TRAVEL_DIRECTION_NONE,
+  ROW_TRAVEL_DIRECTION_FORWARD,
+  ROW_TRAVEL_DIRECTION_BACKWARD
+} RowTravelDirection;
+
+typedef enum {
+  ROW_LABEL_POSITION_START,
+  ROW_LABEL_POSITION_CENTER,
+  ROW_LABEL_POSITION_END
+} RowLabelPosition;
+
+typedef struct {
+  /* Not owned */
+  GtkWidget *add_button;
+  GtkWidget *filter_entry;
+  GtkWidget *list;
+  GtkWidget *scrolledwindow;
+  GtkAdjustment *adjustment;
+  GnomeXkbInfo *xkb_info;
+  GHashTable *ibus_engines;
+
+  /* Owned */
+  GtkWidget *more_item;
+  GtkWidget *no_results;
+  GHashTable *locales;
+  GHashTable *locales_by_language;
+  gboolean showing_extra;
+  gchar **filter_words;
+} CcInputChooserPrivate;
+
+#define GET_PRIVATE(chooser) ((CcInputChooserPrivate *) g_object_get_data (G_OBJECT (chooser), "private"))
 #define WID(name) ((GtkWidget *) gtk_builder_get_object (builder, name))
 
-enum {
-        NAME_COLUMN,
-        TYPE_COLUMN,
-        ID_COLUMN,
-        SETUP_COLUMN,
-        N_COLUMNS
-};
+typedef struct {
+  gchar *id;
+  gchar *name;
+  gchar *unaccented_name;
+  gchar *untranslated_name;
+  GtkWidget *default_input_source_widget;
+  GtkWidget *locale_widget;
+  GtkWidget *back_widget;
+  GHashTable *layout_widgets_by_id;
+  GHashTable *engine_widgets_by_id;
+} LocaleInfo;
 
 static void
-filter_clear (GtkEntry             *entry,
-              GtkEntryIconPosition  icon_pos,
-              GdkEvent             *event,
-              gpointer              user_data)
+locale_info_free (gpointer data)
 {
-  gtk_entry_set_text (entry, "");
-}
+  LocaleInfo *info = data;
 
-static gchar **search_pattern_list;
+  g_free (info->id);
+  g_free (info->name);
+  g_free (info->unaccented_name);
+  g_free (info->untranslated_name);
+  g_object_unref (info->default_input_source_widget);
+  g_object_unref (info->locale_widget);
+  g_object_unref (info->back_widget);
+  g_hash_table_destroy (info->layout_widgets_by_id);
+  g_hash_table_destroy (info->engine_widgets_by_id);
+  g_free (info);
+}
 
 static void
-filter_changed (GtkBuilder *builder)
+set_row_widget_margins (GtkWidget *widget)
 {
-  GtkTreeModelFilter *filtered_model;
-  GtkTreeView *tree_view;
-  GtkTreeSelection *selection;
-  GtkTreeIter selected_iter;
-  GtkWidget *filter_entry;
-  const gchar *pattern;
-  gchar *upattern;
-
-  filter_entry = WID ("input_source_filter");
-  pattern = gtk_entry_get_text (GTK_ENTRY (filter_entry));
-  upattern = g_utf8_strup (pattern, -1);
-  if (!g_strcmp0 (pattern, ""))
-    g_object_set (G_OBJECT (filter_entry),
-                  "secondary-icon-name", "edit-find-symbolic",
-                  "secondary-icon-activatable", FALSE,
-                  "secondary-icon-sensitive", FALSE,
-                  NULL);
+  gtk_widget_set_margin_left (widget, 20);
+  gtk_widget_set_margin_right (widget, 20);
+  gtk_widget_set_margin_top (widget, 6);
+  gtk_widget_set_margin_bottom (widget, 6);
+}
+
+static GtkWidget *
+padded_label_new (const gchar        *text,
+                  RowLabelPosition    position,
+                  RowTravelDirection  direction,
+                  gboolean            dim_label)
+{
+  GtkWidget *widget;
+  GtkWidget *label;
+  GtkWidget *arrow;
+  gdouble alignment;
+  gboolean rtl;
+
+  rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL);
+
+  if (position == ROW_LABEL_POSITION_START)
+    alignment = 0.0;
+  else if (position == ROW_LABEL_POSITION_CENTER)
+    alignment = 0.5;
   else
-    g_object_set (G_OBJECT (filter_entry),
-                  "secondary-icon-name", "edit-clear-symbolic",
-                  "secondary-icon-activatable", TRUE,
-                  "secondary-icon-sensitive", TRUE,
-                  NULL);
-
-  if (search_pattern_list != NULL)
-    g_strfreev (search_pattern_list);
-
-  search_pattern_list = g_strsplit (upattern, " ", -1);
-  g_free (upattern);
-  filtered_model = GTK_TREE_MODEL_FILTER (gtk_builder_get_object (builder, "filtered_input_source_model"));
-  gtk_tree_model_filter_refilter (filtered_model);
-
-  tree_view = GTK_TREE_VIEW (WID ("filtered_input_source_list"));
-  selection = gtk_tree_view_get_selection (tree_view);
-  if (gtk_tree_selection_get_selected (selection, NULL, &selected_iter))
+    alignment = 1.0;
+
+  widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+
+  if (direction == ROW_TRAVEL_DIRECTION_BACKWARD)
     {
-      GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (filtered_model),
-                                                   &selected_iter);
-      gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5, 0.5);
-      gtk_tree_path_free (path);
+      arrow = gtk_image_new_from_icon_name (rtl ? ARROW_NEXT : ARROW_PREV,
+                                            GTK_ICON_SIZE_MENU);
+      gtk_box_pack_start (GTK_BOX (widget), arrow, FALSE, TRUE, 0);
     }
-  else
+
+  label = gtk_label_new (text);
+  gtk_misc_set_alignment (GTK_MISC (label), alignment, 0.5);
+  set_row_widget_margins (label);
+  gtk_box_pack_start (GTK_BOX (widget), label, TRUE, TRUE, 0);
+  if (dim_label)
+    gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+
+  if (direction == ROW_TRAVEL_DIRECTION_FORWARD)
     {
-      GtkTreeIter iter;
-      if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (filtered_model), &iter))
-        gtk_tree_selection_select_iter (selection, &iter);
+      arrow = gtk_image_new_from_icon_name (rtl ? ARROW_PREV : ARROW_NEXT,
+                                            GTK_ICON_SIZE_MENU);
+      gtk_box_pack_start (GTK_BOX (widget), arrow, FALSE, TRUE, 0);
     }
+
+  return widget;
+}
+
+static GtkWidget *
+more_widget_new (void)
+{
+  GtkWidget *widget;
+
+  widget = padded_label_new ("â", ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_NONE, FALSE);
+  gtk_widget_set_tooltip_text (widget, _("Moreâ"));
+
+  return widget;
+}
+
+static GtkWidget *
+no_results_widget_new (void)
+{
+  return padded_label_new (_("No input sources found"), ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_NONE, TRUE);
+}
+
+static GtkWidget *
+back_widget_new (const gchar *text)
+{
+  return padded_label_new (text, ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_BACKWARD, TRUE);
+}
+
+static GtkWidget *
+locale_widget_new (const gchar *text)
+{
+  return padded_label_new (text, ROW_LABEL_POSITION_START, ROW_TRAVEL_DIRECTION_FORWARD, FALSE);
+}
+
+static GtkWidget *
+locale_separator_widget_new (const gchar *text)
+{
+  return padded_label_new (text, ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_NONE, TRUE);
+}
+
+static GtkWidget *
+input_source_widget_new (GtkWidget   *chooser,
+                         const gchar *type,
+                         const gchar *id)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  GtkWidget *widget;
+
+  if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
+    {
+      const gchar *display_name;
+
+      gnome_xkb_info_get_layout_info (priv->xkb_info, id, &display_name, NULL, NULL, NULL);
+
+      widget = padded_label_new (display_name,
+                                 ROW_LABEL_POSITION_START,
+                                 ROW_TRAVEL_DIRECTION_NONE,
+                                 FALSE);
+      g_object_set_data (G_OBJECT (widget), "name", (gpointer) display_name);
+      g_object_set_data_full (G_OBJECT (widget), "unaccented-name",
+                              cc_util_normalize_casefold_and_unaccent (display_name), g_free);
+    }
+  else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
+    {
+#ifdef HAVE_IBUS
+      gchar *display_name;
+      GtkWidget *image;
+
+      display_name = engine_get_display_name (g_hash_table_lookup (priv->ibus_engines, id));
+
+      widget = padded_label_new (display_name,
+                                 ROW_LABEL_POSITION_START,
+                                 ROW_TRAVEL_DIRECTION_NONE,
+                                 FALSE);
+      image = gtk_image_new_from_icon_name ("system-run-symbolic", GTK_ICON_SIZE_MENU);
+      set_row_widget_margins (image);
+      gtk_box_pack_start (GTK_BOX (widget), image, FALSE, TRUE, 0);
+
+      g_object_set_data_full (G_OBJECT (widget), "name", display_name, g_free);
+      g_object_set_data_full (G_OBJECT (widget), "unaccented-name",
+                              cc_util_normalize_casefold_and_unaccent (display_name), g_free);
+#else
+      widget = NULL;
+#endif
+    }
+
+  if (widget)
+    {
+      g_object_set_data (G_OBJECT (widget), "type", (gpointer) type);
+      g_object_set_data (G_OBJECT (widget), "id", (gpointer) id);
+    }
+
+  return widget;
 }
 
 static void
-selection_changed (GtkTreeSelection *selection,
-                   GtkBuilder       *builder)
+remove_all_children (GtkContainer *container)
 {
-  gtk_widget_set_sensitive (WID ("ok-button"),
-                            gtk_tree_selection_get_selected (selection, NULL, NULL));
+  GList *list, *l;
+
+  list = gtk_container_get_children (container);
+  for (l = list; l; l = l->next)
+    gtk_container_remove (container, (GtkWidget *) l->data);
+  g_list_free (list);
 }
 
 static void
-row_activated (GtkTreeView       *tree_view,
-               GtkTreePath       *path,
-               GtkTreeViewColumn *column,
-               GtkBuilder        *builder)
+set_fixed_size (GtkWidget *chooser)
 {
-  GtkWidget *add_button;
-  GtkWidget *dialog;
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  GtkPolicyType policy;
+  gint width, height;
+
+  gtk_scrolled_window_get_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow), &policy, NULL);
+  if (policy == GTK_POLICY_AUTOMATIC)
+    return;
+
+  /* Don't let it automatically get wider than the main CC window nor
+     get taller than the initial height */
+  gtk_window_get_size (gtk_window_get_transient_for (GTK_WINDOW (chooser)),
+                       &width, NULL);
+  gtk_window_get_size (GTK_WINDOW (chooser), NULL, &height);
+  gtk_widget_set_size_request (chooser, width * MAIN_WINDOW_WIDTH_RATIO, height);
+
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow),
+                                  GTK_POLICY_AUTOMATIC,
+                                  GTK_POLICY_AUTOMATIC);
+}
+
+static void
+add_input_source_widgets_for_locale (GtkWidget  *chooser,
+                                     LocaleInfo *info)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  GtkWidget *widget;
+  GHashTableIter iter;
+  const gchar *id;
+
+  if (info->default_input_source_widget)
+    gtk_container_add (GTK_CONTAINER (priv->list), info->default_input_source_widget);
+
+  g_hash_table_iter_init (&iter, info->layout_widgets_by_id);
+  while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &widget))
+    gtk_container_add (GTK_CONTAINER (priv->list), widget);
+
+  g_hash_table_iter_init (&iter, info->engine_widgets_by_id);
+  while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &widget))
+    gtk_container_add (GTK_CONTAINER (priv->list), widget);
+}
+
+static void
+show_input_sources_for_locale (GtkWidget   *chooser,
+                               LocaleInfo  *info)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+
+  set_fixed_size (chooser);
+
+  remove_all_children (GTK_CONTAINER (priv->list));
+
+  if (!info->back_widget)
+    {
+      info->back_widget = g_object_ref_sink (back_widget_new (info->name));
+      g_object_set_data (G_OBJECT (info->back_widget), "back", GINT_TO_POINTER (TRUE));
+      g_object_set_data (G_OBJECT (info->back_widget), "locale-info", info);
+    }
+  gtk_container_add (GTK_CONTAINER (priv->list), info->back_widget);
+
+  add_input_source_widgets_for_locale (chooser, info);
 
-  add_button = WID ("ok-button");
-  dialog = WID ("input_source_chooser");
-  if (gtk_widget_is_sensitive (add_button))
-    gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+  gtk_widget_show_all (priv->list);
+
+  gtk_adjustment_set_value (priv->adjustment,
+                            gtk_adjustment_get_lower (priv->adjustment));
+  egg_list_box_set_separator_funcs (EGG_LIST_BOX (priv->list), NULL, NULL, NULL);
+  egg_list_box_refilter (EGG_LIST_BOX (priv->list));
+  egg_list_box_set_selection_mode (EGG_LIST_BOX (priv->list), GTK_SELECTION_SINGLE);
+
+  if (gtk_widget_is_visible (priv->filter_entry))
+    gtk_widget_grab_focus (priv->filter_entry);
+}
+
+static gboolean
+is_current_locale (const gchar *locale)
+{
+  return g_strcmp0 (setlocale (LC_CTYPE, NULL), locale) == 0;
 }
 
 static void
-entry_activated (GtkBuilder *builder,
-                 gpointer    data)
+show_locale_widgets (GtkWidget *chooser)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  GHashTable *initial;
+  LocaleInfo *info;
+  GHashTableIter iter;
+
+  remove_all_children (GTK_CONTAINER (priv->list));
+
+  if (!priv->showing_extra)
+    initial = cc_common_language_get_initial_languages ();
+
+  g_hash_table_iter_init (&iter, priv->locales);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
+    {
+      if (!info->default_input_source_widget &&
+          !g_hash_table_size (info->layout_widgets_by_id) &&
+          !g_hash_table_size (info->engine_widgets_by_id))
+        continue;
+
+      if (!info->locale_widget)
+        {
+          info->locale_widget = g_object_ref_sink (locale_widget_new (info->name));
+          g_object_set_data (G_OBJECT (info->locale_widget), "locale-info", info);
+
+          if (!priv->showing_extra &&
+              !g_hash_table_contains (initial, info->id) &&
+              !is_current_locale (info->id))
+            g_object_set_data (G_OBJECT (info->locale_widget), "is-extra", GINT_TO_POINTER (TRUE));
+        }
+      gtk_container_add (GTK_CONTAINER (priv->list), info->locale_widget);
+    }
+
+  gtk_container_add (GTK_CONTAINER (priv->list), priv->more_item);
+
+  gtk_widget_show_all (priv->list);
+
+  gtk_adjustment_set_value (priv->adjustment,
+                            gtk_adjustment_get_lower (priv->adjustment));
+  egg_list_box_set_separator_funcs (EGG_LIST_BOX (priv->list), NULL, NULL, NULL);
+  egg_list_box_refilter (EGG_LIST_BOX (priv->list));
+  egg_list_box_set_selection_mode (EGG_LIST_BOX (priv->list), GTK_SELECTION_NONE);
+
+  if (gtk_widget_is_visible (priv->filter_entry))
+    gtk_widget_grab_focus (priv->filter_entry);
+
+  if (!priv->showing_extra)
+    g_hash_table_destroy (initial);
+}
+
+static gint
+list_sort (gconstpointer a,
+           gconstpointer b,
+           gpointer      data)
 {
-  row_activated (NULL, NULL, NULL, builder);
+  GtkWidget *chooser = data;
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  LocaleInfo *ia;
+  LocaleInfo *ib;
+  const gchar *la;
+  const gchar *lb;
+  gint retval;
+
+  /* Always goes at the start */
+  if (a == priv->no_results)
+    return -1;
+  if (b == priv->no_results)
+    return 1;
+
+  /* Always goes at the end */
+  if (a == priv->more_item)
+    return 1;
+  if (b == priv->more_item)
+    return -1;
+
+  ia = g_object_get_data (G_OBJECT (a), "locale-info");
+  ib = g_object_get_data (G_OBJECT (b), "locale-info");
+
+  /* The "Other" locale always goes at the end */
+  if (!ia->id[0] && ib->id[0])
+    return 1;
+  else if (ia->id[0] && !ib->id[0])
+    return -1;
+
+  retval = g_strcmp0 (ia->name, ib->name);
+  if (retval)
+    return retval;
+
+  la = g_object_get_data (G_OBJECT (a), "name");
+  lb = g_object_get_data (G_OBJECT (b), "name");
+
+  /* Only input sources have a "name" property and they should always
+     go after their respective heading */
+  if (la && !lb)
+    return 1;
+  else if (!la && lb)
+    return -1;
+  else if (!la && !lb)
+    return 0; /* Shouldn't happen */
+
+  /* The default input source always goes first in its group */
+  if (g_object_get_data (G_OBJECT (a), "default"))
+    return -1;
+  if (g_object_get_data (G_OBJECT (b), "default"))
+    return 1;
+
+  return g_strcmp0 (la, lb);
 }
 
 static gboolean
-filter_func (GtkTreeModel *model,
-             GtkTreeIter  *iter,
-             gpointer      data)
+match_all (gchar       **words,
+           const gchar  *str)
 {
-  gchar *name = NULL;
-  gchar **pattern;
-  gboolean rv = TRUE;
+  gchar **w;
 
-  if (search_pattern_list == NULL || search_pattern_list[0] == NULL)
+  for (w = words; *w; ++w)
+    if (!strstr (str, *w))
+      return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+list_filter (GtkWidget *child,
+             gpointer   user_data)
+{
+  GtkDialog *chooser = user_data;
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  LocaleInfo *info;
+  gboolean is_extra;
+  const gchar *source_name;
+
+  if (child == priv->more_item)
+    return !priv->showing_extra;
+
+  /* We hide this in the after-refilter handler below. */
+  if (child == priv->no_results)
     return TRUE;
 
-  gtk_tree_model_get (model, iter,
-                      NAME_COLUMN, &name,
-                      -1);
+  is_extra = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (child), "is-extra"));
 
-  pattern = search_pattern_list;
-  do {
-    gboolean is_pattern_found = FALSE;
-    gchar *udesc = g_utf8_strup (name, -1);
-    if (udesc != NULL && g_strstr_len (udesc, -1, *pattern))
-      {
-        is_pattern_found = TRUE;
-      }
-    g_free (udesc);
+  if (!priv->showing_extra && is_extra)
+    return FALSE;
 
-    if (!is_pattern_found)
-      {
-        rv = FALSE;
-        break;
-      }
+  if (!priv->filter_words)
+    return TRUE;
+
+  info = g_object_get_data (G_OBJECT (child), "locale-info");
+
+  if (match_all (priv->filter_words, info->unaccented_name))
+    return TRUE;
+
+  if (match_all (priv->filter_words, info->untranslated_name))
+    return TRUE;
+
+  source_name = g_object_get_data (G_OBJECT (child), "unaccented-name");
+  if (source_name && match_all (priv->filter_words, source_name))
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+update_separator (GtkWidget **separator,
+                  GtkWidget  *child,
+                  GtkWidget  *before,
+                  gpointer    user_data)
+{
+  LocaleInfo *child_info = NULL;
+  LocaleInfo *before_info = NULL;
+
+  if (*separator)
+    return;
+
+  if (child)
+    child_info = g_object_get_data (G_OBJECT (child), "locale-info");
+
+  if (before)
+    before_info = g_object_get_data (G_OBJECT (before), "locale-info");
+
+  if (child_info == before_info || !child_info)
+    return;
+
+  *separator = locale_separator_widget_new (child_info->name);
+  g_object_ref_sink (*separator);
+  gtk_widget_show_all (*separator);
+}
+
+static void
+show_filter_widgets (GtkWidget *chooser)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  LocaleInfo *info;
+  GHashTableIter iter;
+
+  remove_all_children (GTK_CONTAINER (priv->list));
+
+  gtk_container_add (GTK_CONTAINER (priv->list), priv->no_results);
+
+  g_hash_table_iter_init (&iter, priv->locales);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
+    add_input_source_widgets_for_locale (chooser, info);
+
+  gtk_widget_show_all (priv->list);
+
+  gtk_adjustment_set_value (priv->adjustment,
+                            gtk_adjustment_get_lower (priv->adjustment));
+  egg_list_box_set_separator_funcs (EGG_LIST_BOX (priv->list), update_separator,
+                                    chooser, NULL);
+  egg_list_box_refilter (EGG_LIST_BOX (priv->list));
+  egg_list_box_set_selection_mode (EGG_LIST_BOX (priv->list), GTK_SELECTION_SINGLE);
+
+  if (gtk_widget_is_visible (priv->filter_entry))
+    gtk_widget_grab_focus (priv->filter_entry);
+}
+
+static gboolean
+strvs_differ (gchar **av,
+              gchar **bv)
+{
+  gchar **a, **b;
+
+  for (a = av, b = bv; *a && *b; ++a, ++b)
+    if (!g_str_equal (*a, *b))
+      return TRUE;
+
+  if (!*a && !*b)
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+filter_changed (GtkWidget *chooser)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  gboolean was_filtering;
+  gchar **previous_words;
+  gchar *filter_contents = NULL;
+
+  previous_words = priv->filter_words;
+  was_filtering = previous_words != NULL;
+
+  filter_contents =
+    cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (priv->filter_entry)));
+
+  if (filter_contents)
+    {
+      priv->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
+      g_free (filter_contents);
+    }
+
+  if (!priv->filter_words || !priv->filter_words[0])
+    {
+      g_clear_pointer (&priv->filter_words, g_strfreev);
+      if (was_filtering)
+        show_locale_widgets (chooser);
+    }
+  else
+    {
+      if (!was_filtering)
+        show_filter_widgets (chooser);
+      else if (strvs_differ (priv->filter_words, previous_words))
+        egg_list_box_refilter (EGG_LIST_BOX (priv->list));
+    }
+
+  g_strfreev (previous_words);
+}
+
+typedef struct {
+  gint count;
+  GtkWidget *ignore;
+} CountChildrenData;
+
+static void
+count_visible_children (GtkWidget *widget,
+                        gpointer   user_data)
+{
+  CountChildrenData *data = user_data;
+  if (widget != data->ignore &&
+      gtk_widget_get_child_visible (widget) &&
+      gtk_widget_get_visible (widget))
+    data->count++;
+}
+
+static void
+end_refilter (EggListBox *list_box,
+              gpointer    user_data)
+{
+  GtkDialog *chooser = user_data;
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  CountChildrenData data = { 0 };
+  gboolean visible;
+
+  data.ignore = priv->no_results;
+
+  gtk_container_foreach (GTK_CONTAINER (list_box),
+                         count_visible_children, &data);
+
+  visible = (data.count == 0);
+
+  gtk_widget_set_visible (priv->no_results, visible);
+  egg_list_box_set_selection_mode (EGG_LIST_BOX (priv->list),
+                                   visible ? GTK_SELECTION_NONE : GTK_SELECTION_SINGLE);
+}
+
+static void
+show_more (GtkWidget *chooser)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+
+  set_fixed_size (chooser);
+
+  gtk_widget_show (priv->filter_entry);
+  gtk_widget_grab_focus (priv->filter_entry);
 
-  } while (*++pattern != NULL);
-  g_free (name);
+  priv->showing_extra = TRUE;
 
-  return rv;
+  egg_list_box_refilter (EGG_LIST_BOX (priv->list));
 }
 
 static void
-populate_model (GtkListStore *store,
-                GnomeXkbInfo *xkb_info,
-                GHashTable   *ibus_engines)
+child_activated (EggListBox *box,
+                 GtkWidget  *child,
+                 GtkWidget  *chooser)
 {
-  GtkTreeIter iter;
-  const gchar *name;
-  GList *sources, *tmp;
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  gpointer data;
 
-  sources = gnome_xkb_info_get_all_layouts (xkb_info);
-  for (tmp = sources; tmp; tmp = tmp->next)
+  if (!child)
+    return;
+
+  if (child == priv->more_item)
+    {
+      show_more (chooser);
+      return;
+    }
+
+  data = g_object_get_data (G_OBJECT (child), "back");
+  if (data)
+    {
+      show_locale_widgets (chooser);
+      return;
+    }
+
+  data = g_object_get_data (G_OBJECT (child), "name");
+  if (data)
     {
-      gnome_xkb_info_get_layout_info (xkb_info, (const gchar *)tmp->data,
-                                      &name, NULL, NULL, NULL);
-      gtk_list_store_append (store, &iter);
-      gtk_list_store_set (store, &iter,
-                          NAME_COLUMN, name,
-                          TYPE_COLUMN, INPUT_SOURCE_TYPE_XKB,
-                          ID_COLUMN, tmp->data,
-                          -1);
+      /* It's an input source, we just want to select it */
+      return;
     }
-  g_list_free (sources);
+
+  data = g_object_get_data (G_OBJECT (child), "locale-info");
+  if (data)
+    {
+      show_input_sources_for_locale (chooser, (LocaleInfo *) data);
+      return;
+    }
+}
+
+static void
+child_selected (EggListBox *box,
+                GtkWidget  *child,
+                GtkWidget  *chooser)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+
+  gtk_widget_set_sensitive (priv->add_button, child != NULL);
+}
+
+static void
+add_default_widget (GtkWidget   *chooser,
+                    LocaleInfo  *info,
+                    const gchar *type,
+                    const gchar *id)
+{
+  info->default_input_source_widget = input_source_widget_new (chooser, type, id);
+  if (info->default_input_source_widget)
+    {
+      g_object_ref_sink (info->default_input_source_widget);
+      g_object_set_data (G_OBJECT (info->default_input_source_widget), "default", GINT_TO_POINTER (TRUE));
+      g_object_set_data (G_OBJECT (info->default_input_source_widget), "locale-info", info);
+    }
+}
+
+static void
+add_widgets_to_table (GtkWidget   *chooser,
+                      LocaleInfo  *info,
+                      GList       *list,
+                      const gchar *type,
+                      const gchar *default_id)
+{
+  GHashTable *table;
+  GtkWidget *widget;
+  const gchar *id;
+
+  if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
+    table = info->layout_widgets_by_id;
+  else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
+    table = info->engine_widgets_by_id;
+  else
+    return;
+
+  while (list)
+    {
+      id = (const gchar *) list->data;
+
+      /* The widget for the default input source lives elsewhere */
+      if (g_strcmp0 (id, default_id))
+        {
+          widget = input_source_widget_new (chooser, type, id);
+          if (widget)
+            {
+              g_object_set_data (G_OBJECT (widget), "locale-info", info);
+              g_hash_table_replace (table, (gpointer) id, g_object_ref_sink (widget));
+            }
+        }
+      list = list->next;
+    }
+}
 
 #ifdef HAVE_IBUS
-  if (ibus_engines)
+static gboolean
+maybe_set_as_default (GtkWidget   *chooser,
+                      LocaleInfo  *info,
+                      const gchar *engine_id)
+{
+  const gchar *type, *id;
+
+  if (!gnome_get_input_source_from_locale (info->id, &type, &id))
+    return FALSE;
+
+  if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS) &&
+      g_str_equal (id, engine_id) &&
+      info->default_input_source_widget == NULL)
     {
-      gchar *display_name;
+      add_default_widget (chooser, info, type, id);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+get_ibus_locale_infos (GtkWidget *chooser)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  GHashTableIter iter;
+  LocaleInfo *info;
+  const gchar *engine_id;
+  IBusEngineDesc *engine;
+
+  if (!priv->ibus_engines)
+    return;
+
+  g_hash_table_iter_init (&iter, priv->ibus_engines);
+  while (g_hash_table_iter_next (&iter, (gpointer *) &engine_id, (gpointer *) &engine))
+    {
+      gchar *lang_code = NULL;
+      gchar *country_code = NULL;
+      const gchar *ibus_locale = ibus_engine_desc_get_language (engine);
+
+      if (gnome_parse_locale (ibus_locale, &lang_code, &country_code, NULL, NULL) &&
+          lang_code != NULL &&
+          country_code != NULL)
+        {
+          gchar *locale = g_strdup_printf ("%s_%s.utf8", lang_code, country_code);
+
+          info = g_hash_table_lookup (priv->locales, locale);
+          if (info)
+            {
+              const gchar *type, *id;
+
+              if (gnome_get_input_source_from_locale (locale, &type, &id) &&
+                  g_str_equal (type, INPUT_SOURCE_TYPE_IBUS) &&
+                  g_str_equal (id, engine_id))
+                {
+                  add_default_widget (chooser, info, type, id);
+                }
+              else
+                {
+                  GList tmp = { 0 };
+                  tmp.data = (gpointer) engine_id;
+                  add_widgets_to_table (chooser, info, &tmp, INPUT_SOURCE_TYPE_IBUS, NULL);
+                }
+            }
+          else
+            {
+              g_warning ("IBus returned locale '%s' that we don't know about", locale);
+            }
 
-      sources = g_hash_table_get_keys (ibus_engines);
-      for (tmp = sources; tmp; tmp = tmp->next)
+          g_free (locale);
+        }
+      else if (lang_code != NULL)
         {
-          display_name = engine_get_display_name (g_hash_table_lookup (ibus_engines, tmp->data));
-          gtk_list_store_append (store, &iter);
-          gtk_list_store_set (store, &iter,
-                              NAME_COLUMN, display_name,
-                              TYPE_COLUMN, INPUT_SOURCE_TYPE_IBUS,
-                              ID_COLUMN, tmp->data,
-                              -1);
-          g_free (display_name);
+          GHashTableIter iter;
+          GHashTable *locales_for_language;
+          gchar *language;
+
+          /* Most IBus engines only specify the language so we try to
+             add them to all locales for that language. */
+
+          language = gnome_get_language_from_code (lang_code, NULL);
+          if (language)
+            locales_for_language = g_hash_table_lookup (priv->locales_by_language, language);
+          else
+            locales_for_language = NULL;
+          g_free (language);
+
+          if (locales_for_language)
+            {
+              g_hash_table_iter_init (&iter, locales_for_language);
+              while (g_hash_table_iter_next (&iter, (gpointer *) &info, NULL))
+                {
+                  if (!maybe_set_as_default (chooser, info, engine_id))
+                    {
+                      GList tmp = { 0 };
+                      tmp.data = (gpointer) engine_id;
+                      add_widgets_to_table (chooser, info, &tmp, INPUT_SOURCE_TYPE_IBUS, NULL);
+                    }
+                }
+            }
+        }
+      else
+        {
+          /* Add it to the "Other" locale */
+          GList tmp = { 0 };
+          tmp.data = (gpointer) engine_id;
+          info = g_hash_table_lookup (priv->locales, "");
+          add_widgets_to_table (chooser, info, &tmp, INPUT_SOURCE_TYPE_IBUS, NULL);
         }
-      g_list_free (sources);
+
+      g_free (country_code);
+      g_free (lang_code);
     }
+}
 #endif
+
+static void
+add_locale_to_table (GHashTable  *table,
+                     const gchar *lang_code,
+                     LocaleInfo  *info)
+{
+  GHashTable *set;
+  gchar *language;
+
+  language = gnome_get_language_from_code (lang_code, NULL);
+
+  set = g_hash_table_lookup (table, language);
+  if (!set)
+    {
+      set = g_hash_table_new (NULL, NULL);
+      g_hash_table_replace (table, g_strdup (language), set);
+    }
+  g_hash_table_add (set, info);
+
+  g_free (language);
+}
+
+static void
+add_ids_to_set (GHashTable *set,
+                GList      *list)
+{
+  while (list)
+    {
+      g_hash_table_add (set, list->data);
+      list = list->next;
+    }
 }
 
+static void
+get_locale_infos (GtkWidget *chooser)
+{
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  GHashTable *layouts_with_locale;
+  LocaleInfo *info;
+  gchar **locale_ids;
+  gchar **locale;
+  GList *list, *l;
+
+  priv->locales = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                         NULL, locale_info_free);
+  priv->locales_by_language = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                     g_free, (GDestroyNotify) g_hash_table_destroy);
+
+  layouts_with_locale = g_hash_table_new (g_str_hash, g_str_equal);
+
+  locale_ids = gnome_get_all_locales ();
+  for (locale = locale_ids; *locale; ++locale)
+    {
+      gchar *lang_code, *country_code;
+      gchar *simple_locale;
+      const gchar *type = NULL;
+      const gchar *id = NULL;
+
+      if (!gnome_parse_locale (*locale, &lang_code, &country_code, NULL, NULL))
+        continue;
+
+      simple_locale = g_strdup_printf ("%s_%s.utf8", lang_code, country_code);
+      if (g_hash_table_contains (priv->locales, simple_locale))
+        {
+          g_free (simple_locale);
+          g_free (country_code);
+          g_free (lang_code);
+          continue;
+        }
+
+      info = g_new0 (LocaleInfo, 1);
+      info->id = simple_locale; /* Take ownership */
+      info->name = gnome_get_language_from_locale (simple_locale, NULL);
+      info->unaccented_name = cc_util_normalize_casefold_and_unaccent (info->name);
+      info->untranslated_name = gnome_get_language_from_locale (simple_locale, "C");
+
+      g_hash_table_replace (priv->locales, simple_locale, info);
+      add_locale_to_table (priv->locales_by_language, lang_code, info);
+
+      if (gnome_get_input_source_from_locale (simple_locale, &type, &id) &&
+          g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
+        {
+          add_default_widget (chooser, info, type, id);
+          g_hash_table_add (layouts_with_locale, (gpointer) id);
+        }
+
+      /* We don't own these ids */
+      info->layout_widgets_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                          NULL, g_object_unref);
+      info->engine_widgets_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                          NULL, g_object_unref);
+
+      list = gnome_xkb_info_get_layouts_for_language (priv->xkb_info, lang_code);
+      add_widgets_to_table (chooser, info, list, INPUT_SOURCE_TYPE_XKB, id);
+      add_ids_to_set (layouts_with_locale, list);
+      g_list_free (list);
+
+      list = gnome_xkb_info_get_layouts_for_country (priv->xkb_info, country_code);
+      add_widgets_to_table (chooser, info, list, INPUT_SOURCE_TYPE_XKB, id);
+      add_ids_to_set (layouts_with_locale, list);
+      g_list_free (list);
+
+      g_free (lang_code);
+      g_free (country_code);
+    }
+  g_strfreev (locale_ids);
+
+  /* Add a "Other" locale to hold the remaining input sources */
+  info = g_new0 (LocaleInfo, 1);
+  info->id = g_strdup ("");
+  info->name = g_strdup (_("Other"));
+  info->unaccented_name = g_strdup ("");
+  info->untranslated_name = g_strdup ("");
+  g_hash_table_replace (priv->locales, info->id, info);
+
+  info->layout_widgets_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                      NULL, g_object_unref);
+  info->engine_widgets_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                      NULL, g_object_unref);
+
+  list = gnome_xkb_info_get_all_layouts (priv->xkb_info);
+  for (l = list; l; l = l->next)
+    if (!g_hash_table_contains (layouts_with_locale, l->data))
+      {
+        GList tmp = { 0 };
+        tmp.data = l->data;
+        add_widgets_to_table (chooser, info, &tmp, INPUT_SOURCE_TYPE_XKB, NULL);
+      }
+
+  g_list_free (list);
+
+  g_hash_table_destroy (layouts_with_locale);
+}
+
+static void
+cc_input_chooser_private_free (gpointer data)
+{
+  CcInputChooserPrivate *priv = data;
+
+  g_object_unref (priv->more_item);
+  g_object_unref (priv->no_results);
+  g_hash_table_destroy (priv->locales);
+  g_hash_table_destroy (priv->locales_by_language);
+  g_strfreev (priv->filter_words);
+  g_free (priv);
+}
 
 GtkWidget *
 cc_input_chooser_new (GtkWindow    *main_window,
@@ -225,78 +1038,76 @@ cc_input_chooser_new (GtkWindow    *main_window,
 {
   GtkBuilder *builder;
   GtkWidget *chooser;
-  GtkWidget *filtered_list;
-  GtkWidget *filter_entry;
-  GtkTreeViewColumn *visible_column;
-  GtkTreeSelection *selection;
-  GtkListStore *model;
-  GtkTreeModelFilter *filtered_model;
-  GtkTreeIter iter;
+  CcInputChooserPrivate *priv;
+  gint width;
+  GError *error = NULL;
 
   builder = gtk_builder_new ();
-  gtk_builder_add_from_resource (builder,
-                                 "/org/gnome/control-center/region/input-chooser.ui",
-                                 NULL);
-  chooser = WID ("input_source_chooser");
+  gtk_builder_add_from_resource (builder, "/org/gnome/control-center/region/input-chooser.ui", &error);
+  if (error)
+    {
+      g_object_unref (builder);
+      g_warning ("failed to load input chooser: %s", error->message);
+      g_error_free (error);
+      return NULL;
+    }
+  chooser = WID ("input-dialog");
+  priv = g_new0 (CcInputChooserPrivate, 1);
+  g_object_set_data_full (G_OBJECT (chooser), "private", priv, cc_input_chooser_private_free);
   g_object_set_data_full (G_OBJECT (chooser), "builder", builder, g_object_unref);
 
-  filtered_list = WID ("filtered_input_source_list");
-  filter_entry = WID ("input_source_filter");
+  priv->xkb_info = xkb_info;
+  priv->ibus_engines = ibus_engines;
 
-  g_object_set_data (G_OBJECT (chooser),
-                     "filtered_input_source_list", filtered_list);
-  visible_column =
-    gtk_tree_view_column_new_with_attributes ("Input Sources",
-                                              gtk_cell_renderer_text_new (),
-                                              "text", NAME_COLUMN,
-                                              NULL);
-
-  gtk_window_set_transient_for (GTK_WINDOW (chooser), main_window);
+  priv->add_button = WID ("add-button");
+  priv->filter_entry = WID ("filter-entry");
+  priv->list = WID ("list");
+  priv->scrolledwindow = WID ("scrolledwindow");
+  priv->adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
 
-  gtk_tree_view_append_column (GTK_TREE_VIEW (filtered_list),
-                               visible_column);
-  /* We handle searching ourselves, thank you. */
-  gtk_tree_view_set_enable_search (GTK_TREE_VIEW (filtered_list), FALSE);
-  gtk_tree_view_set_search_column (GTK_TREE_VIEW (filtered_list), -1);
+  priv->more_item = g_object_ref_sink (more_widget_new ());
+  priv->no_results = g_object_ref_sink (no_results_widget_new ());
 
-  g_signal_connect_swapped (G_OBJECT (filter_entry), "activate",
-                            G_CALLBACK (entry_activated), builder);
-  g_signal_connect_swapped (G_OBJECT (filter_entry), "notify::text",
-                            G_CALLBACK (filter_changed), builder);
+  egg_list_box_set_adjustment (EGG_LIST_BOX (priv->list), priv->adjustment);
+  egg_list_box_set_filter_func (EGG_LIST_BOX (priv->list), list_filter, chooser, NULL);
+  egg_list_box_set_sort_func (EGG_LIST_BOX (priv->list), list_sort, chooser, NULL);
+  g_signal_connect (priv->list, "child-activated", G_CALLBACK (child_activated), chooser);
+  g_signal_connect (priv->list, "child-selected", G_CALLBACK (child_selected), chooser);
+  g_signal_connect_after (priv->list, "refilter", G_CALLBACK (end_refilter), chooser);
 
-  g_signal_connect (G_OBJECT (filter_entry), "icon-release",
-                    G_CALLBACK (filter_clear), NULL);
+  g_signal_connect_swapped (priv->filter_entry, "changed", G_CALLBACK (filter_changed), chooser);
 
-  filtered_model = GTK_TREE_MODEL_FILTER (gtk_builder_get_object (builder, "filtered_input_source_model"));
-  model = GTK_LIST_STORE (gtk_builder_get_object (builder, "input_source_model"));
-
-  g_object_set_data_full (G_OBJECT (chooser), "xkb-info", g_object_ref (xkb_info), g_object_unref);
-  g_object_set_data_full (G_OBJECT (chooser), "ibus-engines", g_hash_table_ref (ibus_engines), (GDestroyNotify)g_hash_table_unref);
-
-  populate_model (model, xkb_info, ibus_engines);
-
-  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
-                                        NAME_COLUMN, GTK_SORT_ASCENDING);
-  gtk_tree_model_filter_set_visible_func (filtered_model,
-                                          filter_func,
-                                          NULL, NULL);
-
-  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (filtered_list));
+  get_locale_infos (chooser);
+#ifdef HAVE_IBUS
+  get_ibus_locale_infos (chooser);
+#endif
+  show_locale_widgets (chooser);
 
-  g_signal_connect (G_OBJECT (selection), "changed",
-                    G_CALLBACK (selection_changed), builder);
+  /* Try to come up with a sensible width */
+  gtk_window_get_size (main_window, &width, NULL);
+  gtk_widget_set_size_request (chooser, width * MAIN_WINDOW_WIDTH_RATIO, -1);
+  gtk_window_set_resizable (GTK_WINDOW (chooser), TRUE);
 
-  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (filtered_model), &iter))
-    gtk_tree_selection_select_iter (selection, &iter);
+  gtk_window_set_transient_for (GTK_WINDOW (chooser), main_window);
 
-  g_signal_connect (G_OBJECT (filtered_list), "row-activated",
-                    G_CALLBACK (row_activated), builder);
+  return chooser;
+}
 
-  gtk_widget_grab_focus (filter_entry);
+void
+cc_input_chooser_set_ibus_engines (GtkWidget  *chooser,
+                                   GHashTable *ibus_engines)
+{
+#ifdef HAVE_IBUS
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
 
-  gtk_widget_show (chooser);
+  /* This should only be called once when IBus shows up in case it
+     wasn't up yet when the user opened the input chooser dialog. */
+  g_return_if_fail (priv->ibus_engines == NULL);
 
-  return chooser;
+  priv->ibus_engines = ibus_engines;
+  get_ibus_locale_infos (chooser);
+  show_locale_widgets (chooser);
+#endif
 }
 
 gboolean
@@ -305,22 +1116,24 @@ cc_input_chooser_get_selected (GtkWidget  *chooser,
                                gchar     **id,
                                gchar     **name)
 {
-  GtkWidget *tv;
-  GtkTreeModel *model;
-  GtkTreeIter iter;
-  GtkTreeSelection *selection;
+  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
+  GtkWidget *selected;
+  const gchar *t, *i, *n;
 
-  tv = g_object_get_data (G_OBJECT (chooser), "filtered_input_source_list");
-  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
-  if (gtk_tree_selection_get_selected (selection, &model, &iter))
-    {
-      gtk_tree_model_get (model, &iter,
-                          TYPE_COLUMN, type,
-                          ID_COLUMN, id,
-                          NAME_COLUMN, name,
-                          -1);
-      return TRUE;
-    }
+  selected = egg_list_box_get_selected_child (EGG_LIST_BOX (priv->list));
+  if (!selected)
+    return FALSE;
 
-  return FALSE;
+  t = g_object_get_data (G_OBJECT (selected), "type");
+  i = g_object_get_data (G_OBJECT (selected), "id");
+  n = g_object_get_data (G_OBJECT (selected), "name");
+
+  if (!t || !i || !n)
+    return FALSE;
+
+  *type = g_strdup (t);
+  *id = g_strdup (i);
+  *name = g_strdup (n);
+
+  return TRUE;
 }
diff --git a/panels/region/cc-input-chooser.h b/panels/region/cc-input-chooser.h
index 394db82..05fa448 100644
--- a/panels/region/cc-input-chooser.h
+++ b/panels/region/cc-input-chooser.h
@@ -32,6 +32,8 @@ G_BEGIN_DECLS
 GtkWidget   *cc_input_chooser_new          (GtkWindow    *parent,
                                             GnomeXkbInfo *xkb_info,
                                             GHashTable   *ibus_engines);
+void         cc_input_chooser_set_ibus_engines (GtkWidget *chooser,
+                                                GHashTable *ibus_engines);
 gboolean     cc_input_chooser_get_selected (GtkWidget    *chooser,
                                             gchar       **type,
                                             gchar       **id,
diff --git a/panels/region/cc-region-panel.c b/panels/region/cc-region-panel.c
index 105547c..6397c39 100644
--- a/panels/region/cc-region-panel.c
+++ b/panels/region/cc-region-panel.c
@@ -527,6 +527,19 @@ update_ibus_active_sources (CcRegionPanel *self)
 }
 
 static void
+update_input_chooser (CcRegionPanel *self)
+{
+        CcRegionPanelPrivate *priv = self->priv;
+        GtkWidget *chooser;
+
+        chooser = g_object_get_data (G_OBJECT (self), "input-chooser");
+        if (!chooser)
+                return;
+
+        cc_input_chooser_set_ibus_engines (chooser, priv->ibus_engines);
+}
+
+static void
 fetch_ibus_engines_result (GObject       *object,
                            GAsyncResult  *result,
                            CcRegionPanel *self)
@@ -559,6 +572,7 @@ fetch_ibus_engines_result (GObject       *object,
         g_list_free (list);
 
         update_ibus_active_sources (self);
+        update_input_chooser (self);
 }
 
 static void
@@ -916,9 +930,6 @@ input_response (GtkWidget *chooser, gint response_id, gpointer data)
         if (response_id == GTK_RESPONSE_OK) {
                 if (cc_input_chooser_get_selected (chooser, &type, &id, &name) &&
                     !input_source_already_added (self, id)) {
-
-                        gtk_widget_destroy (chooser);
-
                         if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS)) {
                                 g_free (type);
                                 type = INPUT_SOURCE_TYPE_IBUS;
@@ -941,9 +952,9 @@ input_response (GtkWidget *chooser, gint response_id, gpointer data)
                         g_free (name);
                         g_clear_object (&app_info);
                 }
-        } else {
-                gtk_widget_destroy (chooser);
         }
+        gtk_widget_destroy (chooser);
+        g_object_set_data (G_OBJECT (self), "input-chooser", NULL);
 }
 
 static void
@@ -964,6 +975,9 @@ show_input_chooser (CcRegionPanel *self)
                 );
         g_signal_connect (chooser, "response",
                           G_CALLBACK (input_response), self);
+        gtk_window_present (GTK_WINDOW (chooser));
+
+        g_object_set_data (G_OBJECT (self), "input-chooser", chooser);
 }
 
 static void
diff --git a/panels/region/input-chooser.ui b/panels/region/input-chooser.ui
index 0a3f346..5d16271 100644
--- a/panels/region/input-chooser.ui
+++ b/panels/region/input-chooser.ui
@@ -1,143 +1,79 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="2.16"/>
-  <object class="GtkListStore" id="input_source_model">
-    <columns>
-      <!-- display name -->
-      <column type="gchararray"/>
-      <!-- input source type -->
-      <column type="gchararray"/>
-      <!-- type specific identifier -->
-      <column type="gchararray"/>
-    </columns>
-  </object>
-  <object class="GtkTreeModelFilter" id="filtered_input_source_model">
-    <property name="child_model">input_source_model</property>
-  </object>
-  <object class="GtkDialog" id="input_source_chooser">
-    <property name="visible">False</property>
-    <property name="can_focus">False</property>
-    <property name="border_width">5</property>
-    <property name="title" translatable="yes">Select an input source</property>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkDialog" id="input-dialog">
+    <property name="title" translatable="yes">Add an Input Source</property>
     <property name="modal">True</property>
-    <property name="window_position">center-on-parent</property>
-    <property name="type_hint">dialog</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="resizable">True</property>
     <child internal-child="vbox">
-      <object class="GtkBox" id="dialog-vbox3">
+      <object class="GtkBox" id="vbox">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="hbtnBox">
+        <property name="spacing">0</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
+            <property name="hscrollbar-policy">never</property>
+            <property name="vscrollbar-policy">never</property>
+            <property name="shadow-type">in</property>
+            <property name="margin-left">6</property>
+            <property name="margin-right">6</property>
+            <property name="margin-top">6</property>
+            <property name="margin-bottom">6</property>
             <child>
-              <object class="GtkButton" id="cancel-button">
-                <property name="label">gtk-cancel</property>
+              <object class="GtkViewport" id="viewport">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_action_appearance">False</property>
-                <property name="use_stock">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="pack_type">end</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="ok-button">
-                <property name="label">gtk-add</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_action_appearance">False</property>
-                <property name="use_stock">True</property>
+                <child>
+                  <object class="EggListBox" id="list">
+                    <property name="visible">True</property>
+                    <property name="can-focus">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="halign">fill</property>
+                    <property name="valign">fill</property>
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="pack_type">end</property>
-                <property name="position">2</property>
-              </packing>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkVBox" id="vbox40">
+          <object class="GtkSearchEntry" id="filter-entry">
+            <property name="visible">False</property>
+            <property name="hexpand">True</property>
+            <property name="margin-left">6</property>
+            <property name="margin-right">6</property>
+            <property name="margin-top">6</property>
+            <property name="margin-bottom">6</property>
+          </object>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="action-area">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="border_width">5</property>
-            <property name="spacing">6</property>
+            <property name="orientation">horizontal</property>
             <child>
-              <object class="GtkVBox" id="vbox1">
+              <object class="GtkButton" id="cancel-button">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="spacing">6</property>
-                <child>
-                  <object class="GtkScrolledWindow" id="scrolledwindow1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hscrollbar_policy">never</property>
-                    <property name="shadow_type">etched-in</property>
-                    <property name="min_content_width">450</property>
-                    <property name="min_content_height">250</property>
-                    <child>
-                      <object class="GtkTreeView" id="filtered_input_source_list">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="model">filtered_input_source_model</property>
-                        <property name="headers_visible">False</property>
-                        <property name="search_column">0</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
+                <property name="label">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="use_underline" >True</property>
               </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkEntry" id="input_source_filter">
+              <object class="GtkButton" id="add-button">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">â</property>
-                <property name="secondary-icon-name">edit-find-symbolic</property>
-                <property name="secondary-icon-activatable">False</property>
-                <property name="secondary-icon-sensitive">False</property>
+                <property name="sensitive">False</property>
+                <property name="label">gtk-add</property>
+                <property name="use_stock">True</property>
+                <property name="use_underline" >True</property>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="pack_type">end</property>
-                <property name="position">1</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
         </child>
       </object>
     </child>
     <action-widgets>
-      <action-widget response="-5">ok-button</action-widget>
+      <action-widget response="-5">add-button</action-widget>
       <action-widget response="-6">cancel-button</action-widget>
     </action-widgets>
   </object>


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